create-better-t-stack 2.14.4 → 2.15.1

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 (37) hide show
  1. package/dist/index.js +20 -16
  2. package/package.json +1 -1
  3. package/templates/examples/ai/native/nativewind/app/(drawer)/ai.tsx.hbs +155 -0
  4. package/templates/examples/ai/native/nativewind/polyfills.js +25 -0
  5. package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +279 -0
  6. package/templates/examples/ai/native/unistyles/polyfills.js +25 -0
  7. package/templates/examples/todo/native/unistyles/app/(drawer)/todos.tsx.hbs +340 -0
  8. package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/_layout.tsx +24 -7
  9. package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx +15 -13
  10. package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx +15 -13
  11. package/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs +25 -9
  12. package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +43 -30
  13. package/templates/frontend/native/nativewind/app/+not-found.tsx +20 -9
  14. package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +3 -0
  15. package/templates/frontend/native/nativewind/app/modal.tsx +9 -7
  16. package/templates/frontend/native/nativewind/components/container.tsx +2 -3
  17. package/templates/frontend/native/nativewind/components/header-button.tsx +24 -29
  18. package/templates/frontend/native/nativewind/components/tabbar-icon.tsx +3 -10
  19. package/templates/frontend/native/nativewind/global.css +36 -11
  20. package/templates/frontend/native/nativewind/lib/constants.ts +8 -8
  21. package/templates/frontend/native/nativewind/{package.json → package.json.hbs} +4 -0
  22. package/templates/frontend/native/nativewind/tailwind.config.js +27 -0
  23. package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx +9 -4
  24. package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx +25 -17
  25. package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx +26 -18
  26. package/templates/frontend/native/unistyles/app/(drawer)/{_layout.tsx → _layout.tsx.hbs} +35 -7
  27. package/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +121 -58
  28. package/templates/frontend/native/unistyles/app/+not-found.tsx +45 -14
  29. package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +9 -6
  30. package/templates/frontend/native/unistyles/app/modal.tsx +18 -14
  31. package/templates/frontend/native/unistyles/components/container.tsx +3 -8
  32. package/templates/frontend/native/unistyles/components/header-button.tsx +33 -28
  33. package/templates/frontend/native/unistyles/components/tabbar-icon.tsx +3 -10
  34. package/templates/frontend/native/unistyles/{package.json → package.json.hbs} +5 -1
  35. package/templates/frontend/native/unistyles/theme.ts +82 -19
  36. package/templates/frontend/native/unistyles/unistyles.ts +4 -4
  37. /package/templates/examples/todo/native/nativewind/app/{todo.tsx.hbs → (drawer)/todos.tsx.hbs} +0 -0
package/dist/index.js CHANGED
@@ -1686,7 +1686,7 @@ async function setupExamples(config) {
1686
1686
  const serverDirExists = await fs.pathExists(serverDir);
1687
1687
  const hasNuxt = frontend.includes("nuxt");
1688
1688
  const hasSvelte = frontend.includes("svelte");
1689
- const hasReact = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
1689
+ const hasReact = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start") || frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
1690
1690
  if (clientDirExists) {
1691
1691
  const dependencies = ["ai"];
1692
1692
  if (hasNuxt) dependencies.push("@ai-sdk/vue");
@@ -2039,7 +2039,9 @@ function getNativeInstructions(isConvex) {
2039
2039
  const exampleUrl = isConvex ? "https://<YOUR_CONVEX_URL>" : "http://<YOUR_LOCAL_IP>:3000";
2040
2040
  const envFileName = ".env";
2041
2041
  const ipNote = isConvex ? "your Convex deployment URL (find after running 'dev:setup')" : "your local IP address";
2042
- return `${pc.yellow("NOTE:")} For Expo connectivity issues, update apps/native/${envFileName} \nwith ${ipNote}:\n${`${envVar}=${exampleUrl}`}\n`;
2042
+ let instructions = `${pc.yellow("NOTE:")} For Expo connectivity issues, update apps/native/${envFileName} \nwith ${ipNote}:\n${`${envVar}=${exampleUrl}`}\n`;
2043
+ if (isConvex) instructions += `\n${pc.yellow("IMPORTANT:")} When using local development with Convex and native apps, ensure you use your local IP address \ninstead of localhost or 127.0.0.1 for proper connectivity.\n`;
2044
+ return instructions;
2043
2045
  }
2044
2046
  function getLintingInstructions(runCmd) {
2045
2047
  return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
@@ -2511,6 +2513,8 @@ async function setupExamplesTemplate(projectDir, context) {
2511
2513
  const webAppDir = path.join(projectDir, "apps/web");
2512
2514
  const serverAppDirExists = await fs.pathExists(serverAppDir);
2513
2515
  const webAppDirExists = await fs.pathExists(webAppDir);
2516
+ const nativeAppDir = path.join(projectDir, "apps/native");
2517
+ const nativeAppDirExists = await fs.pathExists(nativeAppDir);
2514
2518
  const hasReactWeb = context.frontend.some((f) => [
2515
2519
  "tanstack-router",
2516
2520
  "react-router",
@@ -2578,6 +2582,17 @@ async function setupExamplesTemplate(projectDir, context) {
2578
2582
  if (await fs.pathExists(exampleWebSolidSrc)) await processAndCopyFiles("**/*", exampleWebSolidSrc, webAppDir, context, false);
2579
2583
  }
2580
2584
  }
2585
+ if (nativeAppDirExists) {
2586
+ const hasNativeWind = context.frontend.includes("native-nativewind");
2587
+ const hasUnistyles = context.frontend.includes("native-unistyles");
2588
+ if (hasNativeWind || hasUnistyles) {
2589
+ let nativeFramework = "";
2590
+ if (hasNativeWind) nativeFramework = "nativewind";
2591
+ else if (hasUnistyles) nativeFramework = "unistyles";
2592
+ const exampleNativeSrc = path.join(exampleBaseDir, `native/${nativeFramework}`);
2593
+ if (await fs.pathExists(exampleNativeSrc)) await processAndCopyFiles("**/*", exampleNativeSrc, nativeAppDir, context, false);
2594
+ }
2595
+ }
2581
2596
  }
2582
2597
  }
2583
2598
  async function handleExtras(projectDir, context) {
@@ -2940,19 +2955,8 @@ async function getExamplesChoice(examples, database, frontends, backend, api) {
2940
2955
  if (backend === "convex") return ["todo"];
2941
2956
  if (backend === "none") return [];
2942
2957
  if (database === "none") return [];
2943
- const onlyNative = frontends && frontends.length === 1 && (frontends[0] === "native-nativewind" || frontends[0] === "native-unistyles");
2944
- if (onlyNative) return [];
2945
- const hasWebFrontend = frontends?.some((f) => [
2946
- "react-router",
2947
- "tanstack-router",
2948
- "tanstack-start",
2949
- "next",
2950
- "nuxt",
2951
- "svelte",
2952
- "solid"
2953
- ].includes(f)) ?? false;
2954
2958
  const noFrontendSelected = !frontends || frontends.length === 0;
2955
- if (!hasWebFrontend && !noFrontendSelected) return [];
2959
+ if (noFrontendSelected) return [];
2956
2960
  let response = [];
2957
2961
  const options = [{
2958
2962
  value: "todo",
@@ -3043,7 +3047,7 @@ async function getFrontendChoice(frontendOptions, backend) {
3043
3047
  return true;
3044
3048
  });
3045
3049
  const webFramework = await select({
3046
- message: "Choose frontend",
3050
+ message: "Choose web",
3047
3051
  options: webOptions,
3048
3052
  initialValue: DEFAULT_CONFIG.frontend[0]
3049
3053
  });
@@ -3055,7 +3059,7 @@ async function getFrontendChoice(frontendOptions, backend) {
3055
3059
  }
3056
3060
  if (frontendTypes.includes("native")) {
3057
3061
  const nativeFramework = await select({
3058
- message: "Choose native framework",
3062
+ message: "Choose native",
3059
3063
  options: [{
3060
3064
  value: "native-nativewind",
3061
3065
  label: "NativeWind",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.14.4",
3
+ "version": "2.15.1",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,155 @@
1
+ import { useRef, useEffect } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ TouchableOpacity,
7
+ ScrollView,
8
+ KeyboardAvoidingView,
9
+ Platform,
10
+ } from "react-native";
11
+ import { useChat } from "@ai-sdk/react";
12
+ import { fetch as expoFetch } from "expo/fetch";
13
+ import { Ionicons } from "@expo/vector-icons";
14
+ import { Container } from "@/components/container";
15
+
16
+ // Utility function to generate API URLs
17
+ const generateAPIUrl = (relativePath: string) => {
18
+ const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL;
19
+ if (!serverUrl) {
20
+ throw new Error("EXPO_PUBLIC_SERVER_URL environment variable is not defined");
21
+ }
22
+
23
+ const path = relativePath.startsWith('/') ? relativePath : `/${relativePath}`;
24
+ return serverUrl.concat(path);
25
+ };
26
+
27
+ export default function AIScreen() {
28
+ const { messages, input, handleInputChange, handleSubmit, error } = useChat({
29
+ fetch: expoFetch as unknown as typeof globalThis.fetch,
30
+ api: generateAPIUrl('/ai'),
31
+ onError: error => console.error(error, 'AI Chat Error'),
32
+ maxSteps: 5,
33
+ });
34
+
35
+ const scrollViewRef = useRef<ScrollView>(null);
36
+
37
+ useEffect(() => {
38
+ scrollViewRef.current?.scrollToEnd({ animated: true });
39
+ }, [messages]);
40
+
41
+ const onSubmit = () => {
42
+ if (input.trim()) {
43
+ handleSubmit();
44
+ }
45
+ };
46
+
47
+ if (error) {
48
+ return (
49
+ <Container>
50
+ <View className="flex-1 justify-center items-center px-4">
51
+ <Text className="text-destructive text-center text-lg mb-4">
52
+ Error: {error.message}
53
+ </Text>
54
+ <Text className="text-muted-foreground text-center">
55
+ Please check your connection and try again.
56
+ </Text>
57
+ </View>
58
+ </Container>
59
+ );
60
+ }
61
+
62
+ return (
63
+ <Container>
64
+ <KeyboardAvoidingView
65
+ className="flex-1"
66
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
67
+ >
68
+ <View className="flex-1 px-4 py-6">
69
+ <View className="mb-6">
70
+ <Text className="text-foreground text-2xl font-bold mb-2">
71
+ AI Chat
72
+ </Text>
73
+ <Text className="text-muted-foreground">
74
+ Chat with our AI assistant
75
+ </Text>
76
+ </View>
77
+
78
+ <ScrollView
79
+ ref={scrollViewRef}
80
+ className="flex-1 mb-4"
81
+ showsVerticalScrollIndicator={false}
82
+ >
83
+ {messages.length === 0 ? (
84
+ <View className="flex-1 justify-center items-center">
85
+ <Text className="text-center text-muted-foreground text-lg">
86
+ Ask me anything to get started!
87
+ </Text>
88
+ </View>
89
+ ) : (
90
+ <View className="space-y-4">
91
+ {messages.map((message) => (
92
+ <View
93
+ key={message.id}
94
+ className={`p-3 rounded-lg ${
95
+ message.role === "user"
96
+ ? "bg-primary/10 ml-8"
97
+ : "bg-card mr-8 border border-border"
98
+ }`}
99
+ >
100
+ <Text className="text-sm font-semibold mb-1 text-foreground">
101
+ {message.role === "user" ? "You" : "AI Assistant"}
102
+ </Text>
103
+ <Text className="text-foreground leading-relaxed">
104
+ {message.content}
105
+ </Text>
106
+ </View>
107
+ ))}
108
+ </View>
109
+ )}
110
+ </ScrollView>
111
+
112
+ <View className="border-t border-border pt-4">
113
+ <View className="flex-row items-end space-x-2">
114
+ <TextInput
115
+ value={input}
116
+ onChange={(e) =>
117
+ handleInputChange({
118
+ ...e,
119
+ target: {
120
+ ...e.target,
121
+ value: e.nativeEvent.text,
122
+ },
123
+ } as unknown as React.ChangeEvent<HTMLInputElement>)
124
+ }
125
+ placeholder="Type your message..."
126
+ placeholderTextColor="#6b7280"
127
+ className="flex-1 border border-border rounded-md px-3 py-2 text-foreground bg-background min-h-[40px] max-h-[120px]"
128
+ onSubmitEditing={(e) => {
129
+ handleSubmit(e);
130
+ e.preventDefault();
131
+ }}
132
+ autoFocus={true}
133
+ />
134
+ <TouchableOpacity
135
+ onPress={onSubmit}
136
+ disabled={!input.trim()}
137
+ className={`p-2 rounded-md ${
138
+ input.trim()
139
+ ? "bg-primary"
140
+ : "bg-muted"
141
+ }`}
142
+ >
143
+ <Ionicons
144
+ name="send"
145
+ size={20}
146
+ color={input.trim() ? "#ffffff" : "#6b7280"}
147
+ />
148
+ </TouchableOpacity>
149
+ </View>
150
+ </View>
151
+ </View>
152
+ </KeyboardAvoidingView>
153
+ </Container>
154
+ );
155
+ }
@@ -0,0 +1,25 @@
1
+ import structuredClone from "@ungap/structured-clone";
2
+ import { Platform } from "react-native";
3
+
4
+ if (Platform.OS !== "web") {
5
+ const setupPolyfills = async () => {
6
+ const { polyfillGlobal } = await import(
7
+ "react-native/Libraries/Utilities/PolyfillFunctions"
8
+ );
9
+
10
+ const { TextEncoderStream, TextDecoderStream } = await import(
11
+ "@stardazed/streams-text-encoding"
12
+ );
13
+
14
+ if (!("structuredClone" in global)) {
15
+ polyfillGlobal("structuredClone", () => structuredClone);
16
+ }
17
+
18
+ polyfillGlobal("TextEncoderStream", () => TextEncoderStream);
19
+ polyfillGlobal("TextDecoderStream", () => TextDecoderStream);
20
+ };
21
+
22
+ setupPolyfills();
23
+ }
24
+
25
+ export {};
@@ -0,0 +1,279 @@
1
+ import { useRef, useEffect } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ TouchableOpacity,
7
+ ScrollView,
8
+ KeyboardAvoidingView,
9
+ Platform,
10
+ } from "react-native";
11
+ import { useChat } from "@ai-sdk/react";
12
+ import { fetch as expoFetch } from "expo/fetch";
13
+ import { Ionicons } from "@expo/vector-icons";
14
+ import { StyleSheet, useUnistyles } from "react-native-unistyles";
15
+ import { Container } from "@/components/container";
16
+
17
+ const generateAPIUrl = (relativePath: string) => {
18
+ const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL;
19
+ if (!serverUrl) {
20
+ throw new Error(
21
+ "EXPO_PUBLIC_SERVER_URL environment variable is not defined",
22
+ );
23
+ }
24
+
25
+ const path = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
26
+ return serverUrl.concat(path);
27
+ };
28
+
29
+ export default function AIScreen() {
30
+ const { theme } = useUnistyles();
31
+ const { messages, input, handleInputChange, handleSubmit, error } = useChat({
32
+ fetch: expoFetch as unknown as typeof globalThis.fetch,
33
+ api: generateAPIUrl("/ai"),
34
+ onError: (error) => console.error(error, "AI Chat Error"),
35
+ maxSteps: 5,
36
+ });
37
+
38
+ const scrollViewRef = useRef<ScrollView>(null);
39
+
40
+ useEffect(() => {
41
+ scrollViewRef.current?.scrollToEnd({ animated: true });
42
+ }, [messages]);
43
+
44
+ const onSubmit = () => {
45
+ if (input.trim()) {
46
+ handleSubmit();
47
+ }
48
+ };
49
+
50
+ if (error) {
51
+ return (
52
+ <Container>
53
+ <View style={styles.errorContainer}>
54
+ <Text style={styles.errorText}>Error: {error.message}</Text>
55
+ <Text style={styles.errorSubtext}>
56
+ Please check your connection and try again.
57
+ </Text>
58
+ </View>
59
+ </Container>
60
+ );
61
+ }
62
+
63
+ return (
64
+ <Container>
65
+ <KeyboardAvoidingView
66
+ style={styles.container}
67
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
68
+ >
69
+ <View style={styles.content}>
70
+ <View style={styles.header}>
71
+ <Text style={styles.headerTitle}>AI Chat</Text>
72
+ <Text style={styles.headerSubtitle}>
73
+ Chat with our AI assistant
74
+ </Text>
75
+ </View>
76
+
77
+ <ScrollView
78
+ ref={scrollViewRef}
79
+ style={styles.messagesContainer}
80
+ showsVerticalScrollIndicator={false}
81
+ >
82
+ {messages.length === 0 ? (
83
+ <View style={styles.emptyContainer}>
84
+ <Text style={styles.emptyText}>
85
+ Ask me anything to get started!
86
+ </Text>
87
+ </View>
88
+ ) : (
89
+ <View style={styles.messagesWrapper}>
90
+ {messages.map((message) => (
91
+ <View
92
+ key={message.id}
93
+ style={[
94
+ styles.messageContainer,
95
+ message.role === "user"
96
+ ? styles.userMessage
97
+ : styles.assistantMessage,
98
+ ]}
99
+ >
100
+ <Text style={styles.messageRole}>
101
+ {message.role === "user" ? "You" : "AI Assistant"}
102
+ </Text>
103
+ <Text style={styles.messageContent}>{message.content}</Text>
104
+ </View>
105
+ ))}
106
+ </View>
107
+ )}
108
+ </ScrollView>
109
+
110
+ <View style={styles.inputSection}>
111
+ <View style={styles.inputContainer}>
112
+ <TextInput
113
+ value={input}
114
+ onChange={(e) =>
115
+ handleInputChange({
116
+ ...e,
117
+ target: {
118
+ ...e.target,
119
+ value: e.nativeEvent.text,
120
+ },
121
+ } as unknown as React.ChangeEvent<HTMLInputElement>)
122
+ }
123
+ placeholder="Type your message..."
124
+ placeholderTextColor={theme.colors.border}
125
+ style={styles.textInput}
126
+ onSubmitEditing={(e) => {
127
+ handleSubmit(e);
128
+ e.preventDefault();
129
+ }}
130
+ autoFocus={true}
131
+ />
132
+ <TouchableOpacity
133
+ onPress={onSubmit}
134
+ disabled={!input.trim()}
135
+ style={[
136
+ styles.sendButton,
137
+ !input.trim() && styles.sendButtonDisabled,
138
+ ]}
139
+ >
140
+ <Ionicons
141
+ name="send"
142
+ size={20}
143
+ color={
144
+ input.trim() ? theme.colors.background : theme.colors.border
145
+ }
146
+ />
147
+ </TouchableOpacity>
148
+ </View>
149
+ </View>
150
+ </View>
151
+ </KeyboardAvoidingView>
152
+ </Container>
153
+ );
154
+ }
155
+
156
+ const styles = StyleSheet.create((theme) => ({
157
+ container: {
158
+ flex: 1,
159
+ },
160
+ content: {
161
+ flex: 1,
162
+ paddingHorizontal: theme.spacing.md,
163
+ paddingVertical: theme.spacing.lg,
164
+ },
165
+ errorContainer: {
166
+ flex: 1,
167
+ justifyContent: "center",
168
+ alignItems: "center",
169
+ paddingHorizontal: theme.spacing.md,
170
+ },
171
+ errorText: {
172
+ color: theme.colors.destructive,
173
+ textAlign: "center",
174
+ fontSize: 18,
175
+ marginBottom: theme.spacing.md,
176
+ },
177
+ errorSubtext: {
178
+ color: theme.colors.typography,
179
+ textAlign: "center",
180
+ fontSize: 16,
181
+ },
182
+ header: {
183
+ marginBottom: theme.spacing.lg,
184
+ },
185
+ headerTitle: {
186
+ fontSize: 28,
187
+ fontWeight: "bold",
188
+ color: theme.colors.typography,
189
+ marginBottom: theme.spacing.sm,
190
+ },
191
+ headerSubtitle: {
192
+ fontSize: 16,
193
+ color: theme.colors.typography,
194
+ },
195
+ messagesContainer: {
196
+ flex: 1,
197
+ marginBottom: theme.spacing.md,
198
+ },
199
+ emptyContainer: {
200
+ flex: 1,
201
+ justifyContent: "center",
202
+ alignItems: "center",
203
+ },
204
+ emptyText: {
205
+ textAlign: "center",
206
+ color: theme.colors.typography,
207
+ fontSize: 18,
208
+ },
209
+ messagesWrapper: {
210
+ gap: theme.spacing.md,
211
+ },
212
+ messageContainer: {
213
+ padding: theme.spacing.md,
214
+ borderRadius: 8,
215
+ },
216
+ userMessage: {
217
+ backgroundColor: theme.colors.primary + "20",
218
+ marginLeft: theme.spacing.xl,
219
+ alignSelf: "flex-end",
220
+ },
221
+ assistantMessage: {
222
+ backgroundColor: theme.colors.background,
223
+ marginRight: theme.spacing.xl,
224
+ borderWidth: 1,
225
+ borderColor: theme.colors.border,
226
+ },
227
+ messageRole: {
228
+ fontSize: 14,
229
+ fontWeight: "600",
230
+ marginBottom: theme.spacing.sm,
231
+ color: theme.colors.typography,
232
+ },
233
+ messageContent: {
234
+ color: theme.colors.typography,
235
+ lineHeight: 20,
236
+ },
237
+ toolInvocations: {
238
+ fontSize: 12,
239
+ color: theme.colors.typography,
240
+ fontFamily: "monospace",
241
+ backgroundColor: theme.colors.border + "40",
242
+ padding: theme.spacing.sm,
243
+ borderRadius: 4,
244
+ marginTop: theme.spacing.sm,
245
+ },
246
+ inputSection: {
247
+ borderTopWidth: 1,
248
+ borderTopColor: theme.colors.border,
249
+ paddingTop: theme.spacing.md,
250
+ },
251
+ inputContainer: {
252
+ flexDirection: "row",
253
+ alignItems: "flex-end",
254
+ gap: theme.spacing.sm,
255
+ },
256
+ textInput: {
257
+ flex: 1,
258
+ borderWidth: 1,
259
+ borderColor: theme.colors.border,
260
+ borderRadius: 8,
261
+ paddingHorizontal: theme.spacing.md,
262
+ paddingVertical: theme.spacing.sm,
263
+ color: theme.colors.typography,
264
+ backgroundColor: theme.colors.background,
265
+ fontSize: 16,
266
+ minHeight: 40,
267
+ maxHeight: 120,
268
+ },
269
+ sendButton: {
270
+ backgroundColor: theme.colors.primary,
271
+ padding: theme.spacing.sm,
272
+ borderRadius: 8,
273
+ justifyContent: "center",
274
+ alignItems: "center",
275
+ },
276
+ sendButtonDisabled: {
277
+ backgroundColor: theme.colors.border,
278
+ },
279
+ }));
@@ -0,0 +1,25 @@
1
+ import structuredClone from "@ungap/structured-clone";
2
+ import { Platform } from "react-native";
3
+
4
+ if (Platform.OS !== "web") {
5
+ const setupPolyfills = async () => {
6
+ const { polyfillGlobal } = await import(
7
+ "react-native/Libraries/Utilities/PolyfillFunctions"
8
+ );
9
+
10
+ const { TextEncoderStream, TextDecoderStream } = await import(
11
+ "@stardazed/streams-text-encoding"
12
+ );
13
+
14
+ if (!("structuredClone" in global)) {
15
+ polyfillGlobal("structuredClone", () => structuredClone);
16
+ }
17
+
18
+ polyfillGlobal("TextEncoderStream", () => TextEncoderStream);
19
+ polyfillGlobal("TextDecoderStream", () => TextDecoderStream);
20
+ };
21
+
22
+ setupPolyfills();
23
+ }
24
+
25
+ export {};