vdb-ai-chat 1.0.7 → 1.0.8
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/dist/chat-widget.js +1 -1
- package/lib/commonjs/components/BetaNotice.js +13 -12
- package/lib/commonjs/components/BetaNotice.js.map +1 -1
- package/lib/commonjs/components/ChatInput.js +59 -49
- package/lib/commonjs/components/ChatInput.js.map +1 -1
- package/lib/commonjs/components/ChatWidget.js +72 -59
- package/lib/commonjs/components/ChatWidget.js.map +1 -1
- package/lib/commonjs/components/MessageBubble.js +67 -52
- package/lib/commonjs/components/MessageBubble.js.map +1 -1
- package/lib/commonjs/components/MessageMetaRow.js +50 -31
- package/lib/commonjs/components/MessageMetaRow.js.map +1 -1
- package/lib/commonjs/components/ProductsListView.js +232 -153
- package/lib/commonjs/components/ProductsListView.js.map +1 -1
- package/lib/commonjs/components/SuggestionsRow.js +27 -24
- package/lib/commonjs/components/SuggestionsRow.js.map +1 -1
- package/lib/commonjs/contexts/ThemeProvider.js +80 -0
- package/lib/commonjs/contexts/ThemeProvider.js.map +1 -0
- package/lib/commonjs/contexts/utils.js +142 -0
- package/lib/commonjs/contexts/utils.js.map +1 -0
- package/lib/module/components/BetaNotice.js +14 -13
- package/lib/module/components/BetaNotice.js.map +1 -1
- package/lib/module/components/ChatInput.js +61 -50
- package/lib/module/components/ChatInput.js.map +1 -1
- package/lib/module/components/ChatWidget.js +76 -61
- package/lib/module/components/ChatWidget.js.map +1 -1
- package/lib/module/components/MessageBubble.js +69 -52
- package/lib/module/components/MessageBubble.js.map +1 -1
- package/lib/module/components/MessageMetaRow.js +52 -32
- package/lib/module/components/MessageMetaRow.js.map +1 -1
- package/lib/module/components/ProductsListView.js +234 -154
- package/lib/module/components/ProductsListView.js.map +1 -1
- package/lib/module/components/SuggestionsRow.js +29 -25
- package/lib/module/components/SuggestionsRow.js.map +1 -1
- package/lib/module/contexts/ThemeProvider.js +75 -0
- package/lib/module/contexts/ThemeProvider.js.map +1 -0
- package/lib/module/contexts/utils.js +136 -0
- package/lib/module/contexts/utils.js.map +1 -0
- package/lib/typescript/components/BetaNotice.d.ts.map +1 -1
- package/lib/typescript/components/ChatInput.d.ts.map +1 -1
- package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble.d.ts +1 -1
- package/lib/typescript/components/MessageBubble.d.ts.map +1 -1
- package/lib/typescript/components/MessageMetaRow.d.ts.map +1 -1
- package/lib/typescript/components/ProductsListView.d.ts.map +1 -1
- package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -1
- package/lib/typescript/contexts/ThemeProvider.d.ts +7 -0
- package/lib/typescript/contexts/ThemeProvider.d.ts.map +1 -0
- package/lib/typescript/contexts/utils.d.ts +136 -0
- package/lib/typescript/contexts/utils.d.ts.map +1 -0
- package/package.json +6 -2
- package/src/components/BetaNotice.tsx +15 -13
- package/src/components/ChatInput.tsx +63 -62
- package/src/components/ChatWidget.tsx +238 -206
- package/src/components/MessageBubble.tsx +113 -74
- package/src/components/MessageMetaRow.tsx +80 -50
- package/src/components/ProductsListView.tsx +242 -183
- package/src/components/SuggestionsRow.tsx +40 -25
- package/src/contexts/ThemeProvider.tsx +87 -0
- package/src/contexts/utils.ts +135 -0
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
Pressable,
|
|
11
11
|
} from "react-native";
|
|
12
12
|
import { getUserCurrencySymbol } from "./utils";
|
|
13
|
+
import { useTheme } from "styled-components/native";
|
|
14
|
+
import { DefaultTheme } from "styled-components/native";
|
|
15
|
+
import styled from "styled-components/native";
|
|
13
16
|
|
|
14
17
|
let ImageComponent: typeof Image = Image;
|
|
15
18
|
if (Platform.OS !== "web") {
|
|
@@ -21,6 +24,93 @@ if (Platform.OS !== "web") {
|
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
// Token configuration for diamond details display
|
|
28
|
+
type TokenStyle = "primary" | "secondary";
|
|
29
|
+
type TokenType =
|
|
30
|
+
| "field"
|
|
31
|
+
| "separator"
|
|
32
|
+
| "lineBreak"
|
|
33
|
+
| "group"
|
|
34
|
+
| "fieldWithSuffix"
|
|
35
|
+
| "priceField";
|
|
36
|
+
|
|
37
|
+
interface TokenConfig {
|
|
38
|
+
type: TokenType;
|
|
39
|
+
field?: string;
|
|
40
|
+
style?: TokenStyle;
|
|
41
|
+
suffix?: string;
|
|
42
|
+
prefix?: string;
|
|
43
|
+
format?: "percent";
|
|
44
|
+
showCurrencyIf?: string;
|
|
45
|
+
items?: TokenConfig[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const DIAMOND_TOKENS: TokenConfig[] = [
|
|
49
|
+
// Row 1: Basic specs
|
|
50
|
+
{ type: "field", field: "shape_long", style: "primary" },
|
|
51
|
+
{
|
|
52
|
+
type: "fieldWithSuffix",
|
|
53
|
+
field: "size",
|
|
54
|
+
style: "secondary",
|
|
55
|
+
suffix: "ct",
|
|
56
|
+
showCurrencyIf: "price_per_carat",
|
|
57
|
+
},
|
|
58
|
+
{ type: "field", field: "color", style: "primary" },
|
|
59
|
+
{ type: "field", field: "clarity_short", style: "primary" },
|
|
60
|
+
{ type: "separator" },
|
|
61
|
+
{
|
|
62
|
+
type: "group",
|
|
63
|
+
items: [
|
|
64
|
+
{ type: "field", field: "cut_short", style: "primary" },
|
|
65
|
+
{ type: "field", field: "polish_short", style: "primary", prefix: "/" },
|
|
66
|
+
{ type: "field", field: "symmetry_short", style: "primary", prefix: "/" },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{ type: "separator" },
|
|
70
|
+
{ type: "field", field: "fluorescence_intensity_short", style: "primary" },
|
|
71
|
+
{ type: "separator" },
|
|
72
|
+
{ type: "field", field: "lab_short", style: "primary" },
|
|
73
|
+
{ type: "separator" },
|
|
74
|
+
{
|
|
75
|
+
type: "field",
|
|
76
|
+
field: "discount_percent",
|
|
77
|
+
style: "secondary",
|
|
78
|
+
format: "percent",
|
|
79
|
+
},
|
|
80
|
+
// Row 2: Measurements & Pricing
|
|
81
|
+
{ type: "lineBreak" },
|
|
82
|
+
{
|
|
83
|
+
type: "field",
|
|
84
|
+
field: "depth_percent",
|
|
85
|
+
style: "secondary",
|
|
86
|
+
prefix: "D",
|
|
87
|
+
format: "percent",
|
|
88
|
+
},
|
|
89
|
+
{ type: "separator" },
|
|
90
|
+
{
|
|
91
|
+
type: "field",
|
|
92
|
+
field: "table_percent",
|
|
93
|
+
style: "secondary",
|
|
94
|
+
prefix: "T",
|
|
95
|
+
format: "percent",
|
|
96
|
+
},
|
|
97
|
+
{ type: "separator" },
|
|
98
|
+
{ type: "field", field: "measurement", style: "secondary" },
|
|
99
|
+
{ type: "separator" },
|
|
100
|
+
{
|
|
101
|
+
type: "priceField",
|
|
102
|
+
field: "price_per_carat",
|
|
103
|
+
style: "secondary",
|
|
104
|
+
suffix: "PC",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: "priceField",
|
|
108
|
+
field: "total_sales_price",
|
|
109
|
+
style: "secondary",
|
|
110
|
+
prefix: " =",
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
|
|
24
114
|
interface ProductsListViewProps {
|
|
25
115
|
data: any[];
|
|
26
116
|
totalResults?: number;
|
|
@@ -29,6 +119,80 @@ interface ProductsListViewProps {
|
|
|
29
119
|
item: any;
|
|
30
120
|
}
|
|
31
121
|
|
|
122
|
+
const renderToken = (
|
|
123
|
+
token: TokenConfig,
|
|
124
|
+
item: any,
|
|
125
|
+
userCurrency: string | null,
|
|
126
|
+
index: number
|
|
127
|
+
): React.ReactNode => {
|
|
128
|
+
const TextView = token.style === "secondary" ? TextStyleTwo : TextStyleOne;
|
|
129
|
+
const currency = userCurrency || "$";
|
|
130
|
+
const theme = useTheme();
|
|
131
|
+
switch (token.type) {
|
|
132
|
+
case "separator":
|
|
133
|
+
return (
|
|
134
|
+
<TextView key={index} theme={theme}>
|
|
135
|
+
{"·"}
|
|
136
|
+
</TextView>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
case "lineBreak":
|
|
140
|
+
return <View key={index} style={styles.lineBreak} />;
|
|
141
|
+
|
|
142
|
+
case "field": {
|
|
143
|
+
let value = item[token.field!] ?? "-";
|
|
144
|
+
if (token.format === "percent" && value !== "-") {
|
|
145
|
+
value = `${value}%`;
|
|
146
|
+
}
|
|
147
|
+
return (
|
|
148
|
+
<React.Fragment key={index}>
|
|
149
|
+
{token.prefix && <TextView theme={theme}>{token.prefix}</TextView>}
|
|
150
|
+
<TextView theme={theme}>{value}</TextView>
|
|
151
|
+
</React.Fragment>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case "fieldWithSuffix": {
|
|
156
|
+
const value = item[token.field!] ?? "-";
|
|
157
|
+
const showSuffix = token.showCurrencyIf
|
|
158
|
+
? item[token.showCurrencyIf]
|
|
159
|
+
: true;
|
|
160
|
+
return (
|
|
161
|
+
<View key={index} style={styles.rowCenter}>
|
|
162
|
+
<TextView theme={theme}>{value}</TextView>
|
|
163
|
+
{showSuffix && token.suffix && (
|
|
164
|
+
<TextView theme={theme}>{token.suffix}</TextView>
|
|
165
|
+
)}
|
|
166
|
+
</View>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
case "priceField": {
|
|
171
|
+
const value = item[token.field!];
|
|
172
|
+
return (
|
|
173
|
+
<View key={index} style={styles.rowCenter}>
|
|
174
|
+
{token.prefix && <TextView theme={theme}>{token.prefix}</TextView>}
|
|
175
|
+
{value && <TextView theme={theme}>{currency}</TextView>}
|
|
176
|
+
<TextView theme={theme}>{value ?? "-"}</TextView>
|
|
177
|
+
{token.suffix && <TextView theme={theme}>{token.suffix}</TextView>}
|
|
178
|
+
</View>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case "group":
|
|
183
|
+
return (
|
|
184
|
+
<View key={index} style={styles.rowCenter}>
|
|
185
|
+
{token.items?.map((subToken, subIndex) =>
|
|
186
|
+
renderToken(subToken, item, userCurrency, subIndex)
|
|
187
|
+
)}
|
|
188
|
+
</View>
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
default:
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
32
196
|
const ProductsListViewComponent: React.FC<ProductsListViewProps> = ({
|
|
33
197
|
data,
|
|
34
198
|
totalResults,
|
|
@@ -37,7 +201,7 @@ const ProductsListViewComponent: React.FC<ProductsListViewProps> = ({
|
|
|
37
201
|
item,
|
|
38
202
|
}) => {
|
|
39
203
|
if (!data || !data.length) return null;
|
|
40
|
-
|
|
204
|
+
const theme = useTheme();
|
|
41
205
|
const [userCurrency, setUserCurrency] = useState<string | null>(null);
|
|
42
206
|
|
|
43
207
|
useEffect(() => {
|
|
@@ -49,168 +213,110 @@ const ProductsListViewComponent: React.FC<ProductsListViewProps> = ({
|
|
|
49
213
|
}, []);
|
|
50
214
|
|
|
51
215
|
return (
|
|
52
|
-
<
|
|
216
|
+
<Wrapper theme={theme}>
|
|
53
217
|
<ScrollView
|
|
54
218
|
showsVerticalScrollIndicator={false}
|
|
55
219
|
contentContainerStyle={styles.listContent}
|
|
56
220
|
>
|
|
57
|
-
{data.map((
|
|
221
|
+
{data.map((dataItem: any, index: number) => (
|
|
58
222
|
<Pressable
|
|
59
|
-
key={
|
|
60
|
-
onPress={() => onItemPress?.(
|
|
223
|
+
key={dataItem.id}
|
|
224
|
+
onPress={() => onItemPress?.(dataItem)}
|
|
61
225
|
// @ts-ignore - hovered is available on web
|
|
62
226
|
style={({ hovered }) => [
|
|
63
227
|
styles.itemContainer,
|
|
64
228
|
hovered && styles.itemHover,
|
|
65
229
|
]}
|
|
66
230
|
>
|
|
67
|
-
<View>
|
|
68
|
-
<View style={styles.
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
{item.shape_long ?? "-"}
|
|
76
|
-
</Text>
|
|
77
|
-
{/* Carat size */}
|
|
78
|
-
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
|
79
|
-
<Text style={styles.textStyleTwo}>{item.size ?? "-"}</Text>
|
|
80
|
-
{item.price_per_carat ? (
|
|
81
|
-
<Text style={styles.textStyleOne}>ct</Text>
|
|
82
|
-
) : (
|
|
83
|
-
<></>
|
|
84
|
-
)}
|
|
85
|
-
</View>
|
|
86
|
-
{/* Color */}
|
|
87
|
-
<Text style={styles.textStyleOne}>{item.color ?? "-"}</Text>
|
|
88
|
-
{/* Clarity */}
|
|
89
|
-
<Text style={styles.textStyleOne}>
|
|
90
|
-
{item.clarity_short ?? "-"}
|
|
91
|
-
</Text>
|
|
92
|
-
<Text style={styles.textStyleOne}>{"·"}</Text>
|
|
93
|
-
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
|
94
|
-
{/* Cut */}
|
|
95
|
-
<Text style={styles.textStyleOne}>
|
|
96
|
-
{item.cut_short ?? "-"}
|
|
97
|
-
</Text>
|
|
98
|
-
<Text style={styles.textStyleOne}>{"/"}</Text>
|
|
99
|
-
{/* Polish */}
|
|
100
|
-
<Text style={styles.textStyleOne}>
|
|
101
|
-
{item.polish_short ?? "-"}
|
|
102
|
-
</Text>
|
|
103
|
-
<Text style={styles.textStyleOne}>{"/"}</Text>
|
|
104
|
-
{/* Symmetry */}
|
|
105
|
-
<Text style={styles.textStyleOne}>
|
|
106
|
-
{item.symmetry_short ?? "-"}
|
|
107
|
-
</Text>
|
|
108
|
-
</View>
|
|
109
|
-
<Text style={styles.textStyleOne}>{"·"}</Text>
|
|
110
|
-
{/* Fluorescence */}
|
|
111
|
-
<Text style={styles.textStyleOne}>
|
|
112
|
-
{item.fluorescence_intensity_short ?? "-"}
|
|
113
|
-
</Text>
|
|
114
|
-
<Text style={styles.textStyleOne}>{"·"}</Text>
|
|
115
|
-
{/* LAB */}
|
|
116
|
-
<Text style={styles.textStyleOne}>
|
|
117
|
-
{item.lab_short ?? "-"}
|
|
118
|
-
</Text>
|
|
119
|
-
<Text style={styles.textStyleOne}>{"·"}</Text>
|
|
120
|
-
{/* Discount */}
|
|
121
|
-
<Text style={styles.textStyleTwo}>
|
|
122
|
-
{item.discount_percent ? item.discount_percent + "%" : "-"}
|
|
123
|
-
</Text>
|
|
124
|
-
<View style={styles.lineBreak} />
|
|
125
|
-
{/* Depth */}
|
|
126
|
-
<Text style={styles.textStyleOne}>{"D"}</Text>
|
|
127
|
-
<Text style={styles.textStyleTwo}>
|
|
128
|
-
{item.depth_percent ? item.depth_percent + "%" : "-"}
|
|
129
|
-
</Text>
|
|
130
|
-
<Text style={styles.textStyleOne}>{"·"}</Text>
|
|
131
|
-
{/* Table */}
|
|
132
|
-
<Text style={styles.textStyleOne}>{"T"}</Text>
|
|
133
|
-
<Text style={styles.textStyleTwo}>
|
|
134
|
-
{item.table_percent ? item.table_percent + "%" : "-"}
|
|
135
|
-
</Text>
|
|
136
|
-
<Text style={styles.textStyleOne}>{"·"}</Text>
|
|
137
|
-
{/* Meas */}
|
|
138
|
-
<Text style={styles.textStyleTwo}>
|
|
139
|
-
{item.measurement ?? "-"}
|
|
140
|
-
</Text>
|
|
141
|
-
<Text style={styles.textStyleOne}>{"·"}</Text>
|
|
142
|
-
{/* Price Per Carat */}
|
|
143
|
-
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
|
144
|
-
{item.price_per_carat ? (
|
|
145
|
-
<Text style={styles.textStyleTwo}>
|
|
146
|
-
{userCurrency ? userCurrency : "$"}
|
|
147
|
-
</Text>
|
|
148
|
-
) : (
|
|
149
|
-
<></>
|
|
150
|
-
)}
|
|
151
|
-
<Text style={styles.textStyleTwo}>
|
|
152
|
-
{item.price_per_carat ?? "-"}
|
|
153
|
-
</Text>
|
|
154
|
-
<Text style={styles.textStyleOne}>{"PC"}</Text>
|
|
155
|
-
</View>
|
|
156
|
-
{/* Total Sales Price */}
|
|
157
|
-
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
|
158
|
-
<Text style={styles.textStyleOne}>{" ="}</Text>
|
|
159
|
-
{item.total_sales_price ? (
|
|
160
|
-
<Text style={styles.textStyleTwo}>
|
|
161
|
-
{userCurrency ? userCurrency : "$"}
|
|
162
|
-
</Text>
|
|
163
|
-
) : (
|
|
164
|
-
<></>
|
|
165
|
-
)}
|
|
166
|
-
<Text style={styles.textStyleTwo}>
|
|
167
|
-
{item.total_sales_price ?? "-"}
|
|
168
|
-
</Text>
|
|
169
|
-
</View>
|
|
170
|
-
</View>
|
|
231
|
+
<View style={styles.left}>
|
|
232
|
+
<View style={styles.serialContainer}>
|
|
233
|
+
<Serial>{index + 1}.</Serial>
|
|
234
|
+
</View>
|
|
235
|
+
<View style={styles.tokens}>
|
|
236
|
+
{DIAMOND_TOKENS.map((token, tokenIndex) =>
|
|
237
|
+
renderToken(token, dataItem, userCurrency, tokenIndex)
|
|
238
|
+
)}
|
|
171
239
|
</View>
|
|
172
240
|
</View>
|
|
173
241
|
</Pressable>
|
|
174
242
|
))}
|
|
175
243
|
</ScrollView>
|
|
176
244
|
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
activeOpacity={0.8}
|
|
180
|
-
onPress={() => onViewAll(item)}
|
|
181
|
-
>
|
|
182
|
-
<Text
|
|
183
|
-
style={styles.buttonText}
|
|
184
|
-
>{`View All ${totalResults} Results`}</Text>
|
|
245
|
+
<ButtonView activeOpacity={0.8} onPress={() => onViewAll(item)}>
|
|
246
|
+
<ButtonText>{`View All ${totalResults} Results`}</ButtonText>
|
|
185
247
|
<ImageComponent
|
|
186
248
|
source={{
|
|
187
249
|
uri: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/right.svg",
|
|
188
250
|
}}
|
|
189
251
|
resizeMode="contain"
|
|
190
|
-
style={{
|
|
252
|
+
style={{
|
|
253
|
+
width: 20,
|
|
254
|
+
height: 20,
|
|
255
|
+
tintColor: theme["primary-cont"] || "#ffffff",
|
|
256
|
+
}}
|
|
191
257
|
/>
|
|
192
|
-
</
|
|
193
|
-
</
|
|
258
|
+
</ButtonView>
|
|
259
|
+
</Wrapper>
|
|
194
260
|
);
|
|
195
261
|
};
|
|
196
262
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
263
|
+
const Wrapper = styled.View<{ theme: DefaultTheme }>`
|
|
264
|
+
padding-horizontal: 8px;
|
|
265
|
+
`;
|
|
266
|
+
|
|
267
|
+
const Serial = styled.Text<{ theme: DefaultTheme }>`
|
|
268
|
+
font-size: 13px;
|
|
269
|
+
color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
270
|
+
theme["core-05"] || "#020001"};
|
|
271
|
+
font-weight: 500;
|
|
272
|
+
`;
|
|
201
273
|
|
|
274
|
+
const TextStyleOne = styled.Text<{ theme: DefaultTheme }>`
|
|
275
|
+
font-size: 13px;
|
|
276
|
+
color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
277
|
+
theme["core-06"] || "#4F4E57"};
|
|
278
|
+
font-weight: 400;
|
|
279
|
+
font-family: "Roboto";
|
|
280
|
+
font-style: normal;
|
|
281
|
+
`;
|
|
282
|
+
|
|
283
|
+
const TextStyleTwo = styled.Text<{ theme: DefaultTheme }>`
|
|
284
|
+
font-size: 13px;
|
|
285
|
+
color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
286
|
+
theme["core-05"] || "#020001"};
|
|
287
|
+
font-weight: 500;
|
|
288
|
+
font-family: "Roboto";
|
|
289
|
+
font-style: normal;
|
|
290
|
+
`;
|
|
291
|
+
|
|
292
|
+
const ButtonView = styled.TouchableOpacity<{ theme: DefaultTheme }>`
|
|
293
|
+
margin-top: 12px;
|
|
294
|
+
margin-horizontal: 12px;
|
|
295
|
+
align-self: center;
|
|
296
|
+
padding-horizontal: 16px;
|
|
297
|
+
padding-vertical: 6px;
|
|
298
|
+
background-color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
299
|
+
theme["primary-bg-static"] || "#292735"};
|
|
300
|
+
border-radius: 8px;
|
|
301
|
+
align-items: center;
|
|
302
|
+
gap: 16px;
|
|
303
|
+
width: 100%;
|
|
304
|
+
flex-direction: row;
|
|
305
|
+
justify-content: center;
|
|
306
|
+
align-content: center;
|
|
307
|
+
`;
|
|
308
|
+
|
|
309
|
+
const ButtonText = styled.Text<{ theme: DefaultTheme }>`
|
|
310
|
+
color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
311
|
+
theme["primary-cont"] || "#ffffff"};
|
|
312
|
+
font-size: 14px;
|
|
313
|
+
font-weight: 600;
|
|
314
|
+
`;
|
|
315
|
+
|
|
316
|
+
const styles = StyleSheet.create({
|
|
202
317
|
listContent: {
|
|
203
318
|
gap: 8,
|
|
204
319
|
},
|
|
205
|
-
|
|
206
|
-
row: {
|
|
207
|
-
flexDirection: "row",
|
|
208
|
-
justifyContent: "space-between",
|
|
209
|
-
alignItems: "center",
|
|
210
|
-
paddingVertical: 6,
|
|
211
|
-
backgroundColor: "#fff",
|
|
212
|
-
},
|
|
213
|
-
|
|
214
320
|
left: {
|
|
215
321
|
flexDirection: "row",
|
|
216
322
|
alignItems: "flex-start",
|
|
@@ -234,34 +340,9 @@ const styles = StyleSheet.create({
|
|
|
234
340
|
width: "100%",
|
|
235
341
|
height: 0,
|
|
236
342
|
},
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
color: "#020001",
|
|
241
|
-
fontWeight: "500",
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
textStyleOne: {
|
|
245
|
-
fontFamily: "Roboto",
|
|
246
|
-
fontSize: 13,
|
|
247
|
-
fontStyle: "normal",
|
|
248
|
-
fontWeight: "400",
|
|
249
|
-
color: "#4F4E57",
|
|
250
|
-
},
|
|
251
|
-
|
|
252
|
-
textStyleTwo: {
|
|
253
|
-
fontFamily: "Roboto",
|
|
254
|
-
fontSize: 13,
|
|
255
|
-
fontStyle: "normal",
|
|
256
|
-
fontWeight: "500",
|
|
257
|
-
color: "#020001",
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
price: {
|
|
261
|
-
fontSize: 13,
|
|
262
|
-
fontWeight: "500",
|
|
263
|
-
color: "#020001",
|
|
264
|
-
textAlign: "left",
|
|
343
|
+
rowCenter: {
|
|
344
|
+
flexDirection: "row",
|
|
345
|
+
alignItems: "center",
|
|
265
346
|
},
|
|
266
347
|
priceContainer: {
|
|
267
348
|
alignItems: "flex-start",
|
|
@@ -275,28 +356,6 @@ const styles = StyleSheet.create({
|
|
|
275
356
|
itemHover: {
|
|
276
357
|
backgroundColor: "#EDEDF2",
|
|
277
358
|
},
|
|
278
|
-
|
|
279
|
-
button: {
|
|
280
|
-
marginTop: 12,
|
|
281
|
-
marginHorizontal: 12,
|
|
282
|
-
alignSelf: "center",
|
|
283
|
-
paddingHorizontal: 16,
|
|
284
|
-
paddingVertical: 6,
|
|
285
|
-
backgroundColor: "#292735",
|
|
286
|
-
borderRadius: 8,
|
|
287
|
-
alignItems: "center",
|
|
288
|
-
gap: 16,
|
|
289
|
-
width: "100%",
|
|
290
|
-
flexDirection: "row",
|
|
291
|
-
justifyContent: "center",
|
|
292
|
-
alignContent: "center",
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
buttonText: {
|
|
296
|
-
color: "#fff",
|
|
297
|
-
fontSize: 14,
|
|
298
|
-
fontWeight: "600",
|
|
299
|
-
},
|
|
300
359
|
});
|
|
301
360
|
|
|
302
361
|
const ProductsListView = memo(ProductsListViewComponent);
|
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
StyleSheet,
|
|
7
7
|
ScrollView,
|
|
8
8
|
} from "react-native";
|
|
9
|
+
import styled from "styled-components/native";
|
|
10
|
+
import { useTheme } from "styled-components/native";
|
|
11
|
+
import { DefaultTheme } from "styled-components/native";
|
|
9
12
|
|
|
10
13
|
interface SuggestionsRowProps {
|
|
11
14
|
suggestions: string[];
|
|
@@ -21,6 +24,7 @@ const SuggestionsRowComponent: React.FC<SuggestionsRowProps> = ({
|
|
|
21
24
|
}) => {
|
|
22
25
|
if (!suggestions.length) return null;
|
|
23
26
|
const ScrollViewComponent = variant === "inline" ? View : ScrollView;
|
|
27
|
+
const theme = useTheme();
|
|
24
28
|
return (
|
|
25
29
|
<ScrollViewComponent
|
|
26
30
|
showsVerticalScrollIndicator={false}
|
|
@@ -32,14 +36,15 @@ const SuggestionsRowComponent: React.FC<SuggestionsRowProps> = ({
|
|
|
32
36
|
>
|
|
33
37
|
{suggestions.map((s, index) => (
|
|
34
38
|
<>
|
|
35
|
-
<
|
|
39
|
+
<ChipButton
|
|
36
40
|
key={`${s}-${index}`}
|
|
37
|
-
|
|
41
|
+
theme={theme}
|
|
42
|
+
variant={variant}
|
|
38
43
|
onPress={() => onSelect(s)}
|
|
39
44
|
activeOpacity={0.75}
|
|
40
45
|
>
|
|
41
|
-
<
|
|
42
|
-
</
|
|
46
|
+
<ChipText>{s}</ChipText>
|
|
47
|
+
</ChipButton>
|
|
43
48
|
{index < suggestions.length - 1 && <View style={styles.spacer} />}
|
|
44
49
|
</>
|
|
45
50
|
))}
|
|
@@ -47,6 +52,37 @@ const SuggestionsRowComponent: React.FC<SuggestionsRowProps> = ({
|
|
|
47
52
|
);
|
|
48
53
|
};
|
|
49
54
|
|
|
55
|
+
const ChipButton = styled.TouchableOpacity<{
|
|
56
|
+
theme: DefaultTheme;
|
|
57
|
+
variant: "inline" | "default";
|
|
58
|
+
}>`
|
|
59
|
+
background-color: ${({
|
|
60
|
+
theme,
|
|
61
|
+
variant,
|
|
62
|
+
}: {
|
|
63
|
+
theme: DefaultTheme;
|
|
64
|
+
variant: "inline" | "default";
|
|
65
|
+
}) =>
|
|
66
|
+
variant === "inline"
|
|
67
|
+
? theme["core-01"] || "#FFFFFF"
|
|
68
|
+
: theme["core-02"] || "#EDEDF2"};
|
|
69
|
+
border-radius: 6px;
|
|
70
|
+
padding-horizontal: 8px;
|
|
71
|
+
padding-vertical: 6px;
|
|
72
|
+
width: 100%;
|
|
73
|
+
align-self: stretch;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
align-items: flex-start;
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const ChipText = styled.Text<{ theme: DefaultTheme }>`
|
|
79
|
+
color: ${({ theme }: { theme: DefaultTheme }) =>
|
|
80
|
+
theme["core-05"] || "#1A1A1A"};
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
text-align: left;
|
|
84
|
+
`;
|
|
85
|
+
|
|
50
86
|
const styles = StyleSheet.create({
|
|
51
87
|
scroll: {
|
|
52
88
|
maxWidth: "80%",
|
|
@@ -71,27 +107,6 @@ const styles = StyleSheet.create({
|
|
|
71
107
|
// inside bubble, rely on bubble background
|
|
72
108
|
gap: 6,
|
|
73
109
|
},
|
|
74
|
-
|
|
75
|
-
chip: {
|
|
76
|
-
backgroundColor: "#EDEDF2",
|
|
77
|
-
borderRadius: 6,
|
|
78
|
-
paddingHorizontal: 8,
|
|
79
|
-
paddingVertical: 6,
|
|
80
|
-
width: "100%",
|
|
81
|
-
alignSelf: "stretch",
|
|
82
|
-
justifyContent: "center",
|
|
83
|
-
alignItems: "flex-start",
|
|
84
|
-
},
|
|
85
|
-
chipInline: {
|
|
86
|
-
backgroundColor: "#FFFFFF",
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
chipText: {
|
|
90
|
-
fontSize: 14,
|
|
91
|
-
color: "#1A1A1A",
|
|
92
|
-
fontWeight: "500",
|
|
93
|
-
textAlign: "left",
|
|
94
|
-
},
|
|
95
110
|
spacer: {
|
|
96
111
|
width: 4,
|
|
97
112
|
height: 4,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import {
|
|
4
|
+
DefaultTheme,
|
|
5
|
+
ThemeProvider as OriginalThemeProvider,
|
|
6
|
+
} from "styled-components/native";
|
|
7
|
+
import { useMediaQuery } from "react-responsive";
|
|
8
|
+
import { DEFAULT_THEME } from "./utils";
|
|
9
|
+
import { DEVICE_SIZE, isIpad } from "../components/utils";
|
|
10
|
+
import { useMemo, useState, useEffect } from "react";
|
|
11
|
+
import { Storage } from "../storage";
|
|
12
|
+
|
|
13
|
+
// Conditionally import expo-device only on native platforms
|
|
14
|
+
let Device: { osName?: string } | null = null;
|
|
15
|
+
if (Platform.OS !== "web") {
|
|
16
|
+
try {
|
|
17
|
+
Device = require("expo-device");
|
|
18
|
+
} catch {
|
|
19
|
+
// expo-device not installed
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
children?: React.ReactElement;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ThemeProvider = ({ children }: Props) => {
|
|
28
|
+
const [theme, setTheme] = useState<Record<string, any>>(DEFAULT_THEME);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const loadTheme = async () => {
|
|
32
|
+
try {
|
|
33
|
+
const rootState = JSON.parse(
|
|
34
|
+
(await Storage.getItem("persist:root")) || "{}"
|
|
35
|
+
);
|
|
36
|
+
const userTheme = rootState.theme ? JSON.parse(rootState.theme) : null;
|
|
37
|
+
if (userTheme) {
|
|
38
|
+
setTheme(userTheme);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Keep default theme on error
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
loadTheme();
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
if (Platform.OS === "web") {
|
|
48
|
+
const mediaQuery = window.matchMedia("print");
|
|
49
|
+
if (mediaQuery.matches) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const mobileQuery = useMediaQuery({ maxWidth: 767 });
|
|
55
|
+
const tabletQuery = useMediaQuery({ minWidth: 767, maxWidth: 1279 });
|
|
56
|
+
const desktopQuery = useMediaQuery({ minWidth: 1279 });
|
|
57
|
+
|
|
58
|
+
const { width } = DEVICE_SIZE;
|
|
59
|
+
const isMobile = mobileQuery || width <= 767;
|
|
60
|
+
const isTablet =
|
|
61
|
+
tabletQuery ||
|
|
62
|
+
(width > 767 && width <= 1279) ||
|
|
63
|
+
(Device?.osName === "iPadOS") ||
|
|
64
|
+
isIpad();
|
|
65
|
+
const isDesktop =
|
|
66
|
+
(desktopQuery || width > 1279) &&
|
|
67
|
+
!(Device?.osName === "iPadOS" || isIpad());
|
|
68
|
+
const isLargeScreen = isDesktop && !isTablet && !isMobile;
|
|
69
|
+
|
|
70
|
+
const media = {
|
|
71
|
+
isMobile,
|
|
72
|
+
isTablet,
|
|
73
|
+
isDesktop,
|
|
74
|
+
isLargeScreen,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const mediaTheme = useMemo(
|
|
78
|
+
() => ({ ...theme, ...media } as DefaultTheme),
|
|
79
|
+
[theme, isMobile, isTablet, isDesktop, isLargeScreen]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<OriginalThemeProvider theme={mediaTheme}>{children}</OriginalThemeProvider>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default ThemeProvider;
|