@uploadista/react-native-core 0.0.3
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/.turbo/turbo-check.log +396 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/package.json +42 -0
- package/src/client/create-uploadista-client.ts +65 -0
- package/src/client/index.ts +4 -0
- package/src/components/CameraUploadButton.tsx +130 -0
- package/src/components/FileUploadButton.tsx +130 -0
- package/src/components/GalleryUploadButton.tsx +199 -0
- package/src/components/UploadList.tsx +214 -0
- package/src/components/UploadProgress.tsx +196 -0
- package/src/components/index.ts +19 -0
- package/src/hooks/index.ts +29 -0
- package/src/hooks/uploadista-context.ts +17 -0
- package/src/hooks/use-camera-upload.ts +38 -0
- package/src/hooks/use-file-upload.ts +40 -0
- package/src/hooks/use-flow-upload.ts +242 -0
- package/src/hooks/use-gallery-upload.ts +65 -0
- package/src/hooks/use-multi-upload.ts +363 -0
- package/src/hooks/use-upload-metrics.ts +82 -0
- package/src/hooks/use-upload.ts +378 -0
- package/src/hooks/use-uploadista-client.ts +23 -0
- package/src/hooks/use-uploadista-context.ts +20 -0
- package/src/index.ts +111 -0
- package/src/types/index.ts +2 -0
- package/src/types/types.ts +359 -0
- package/src/types/upload-input.ts +1 -0
- package/src/utils/fileHelpers.ts +201 -0
- package/src/utils/index.ts +36 -0
- package/src/utils/permissions.ts +177 -0
- package/src/utils/uriHelpers.ts +148 -0
- package/test-compile.ts +5 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { type ReactNode, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
} from "react-native";
|
|
9
|
+
import { useFileUpload } from "../hooks";
|
|
10
|
+
import type { UseFileUploadOptions } from "../types";
|
|
11
|
+
import { UploadProgress } from "./UploadProgress";
|
|
12
|
+
|
|
13
|
+
export interface FileUploadButtonProps {
|
|
14
|
+
/** Options for file upload */
|
|
15
|
+
options?: UseFileUploadOptions;
|
|
16
|
+
/** Button label text */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Custom button content */
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
/** Callback when upload completes successfully */
|
|
21
|
+
onSuccess?: (result: unknown) => void;
|
|
22
|
+
/** Callback when upload fails */
|
|
23
|
+
onError?: (error: Error) => void;
|
|
24
|
+
/** Callback when upload is cancelled */
|
|
25
|
+
onCancel?: () => void;
|
|
26
|
+
/** Whether to show progress inline */
|
|
27
|
+
showProgress?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Button component for document/file selection and upload
|
|
32
|
+
* Generic file picker with progress display
|
|
33
|
+
*/
|
|
34
|
+
export function FileUploadButton({
|
|
35
|
+
options,
|
|
36
|
+
label = "Choose File",
|
|
37
|
+
children,
|
|
38
|
+
onSuccess,
|
|
39
|
+
onError,
|
|
40
|
+
onCancel,
|
|
41
|
+
showProgress = true,
|
|
42
|
+
}: FileUploadButtonProps) {
|
|
43
|
+
const { state, pickAndUpload } = useFileUpload(options);
|
|
44
|
+
|
|
45
|
+
const handlePress = async () => {
|
|
46
|
+
try {
|
|
47
|
+
await pickAndUpload();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
if (
|
|
51
|
+
error.message.includes("cancelled") ||
|
|
52
|
+
error.message.includes("aborted")
|
|
53
|
+
) {
|
|
54
|
+
onCancel?.();
|
|
55
|
+
} else {
|
|
56
|
+
onError?.(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const isLoading = state.status === "uploading";
|
|
63
|
+
const isDisabled = isLoading || state.status === "aborted";
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (state.status === "success" && state.result) {
|
|
67
|
+
onSuccess?.(state.result);
|
|
68
|
+
}
|
|
69
|
+
}, [state.status, state.result, onSuccess]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (state.status === "error" && state.error) {
|
|
73
|
+
onError?.(state.error);
|
|
74
|
+
}
|
|
75
|
+
}, [state.status, state.error, onError]);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<View style={styles.container}>
|
|
79
|
+
<Pressable
|
|
80
|
+
style={[styles.button, isDisabled && styles.buttonDisabled]}
|
|
81
|
+
onPress={handlePress}
|
|
82
|
+
disabled={isDisabled}
|
|
83
|
+
>
|
|
84
|
+
{isLoading && (
|
|
85
|
+
<ActivityIndicator
|
|
86
|
+
size="small"
|
|
87
|
+
color="#FFFFFF"
|
|
88
|
+
style={styles.spinner}
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
<Text style={styles.buttonText}>{children || label}</Text>
|
|
92
|
+
</Pressable>
|
|
93
|
+
{showProgress && state.status !== "idle" && (
|
|
94
|
+
<View style={styles.progressContainer}>
|
|
95
|
+
<UploadProgress state={state} label="File upload" />
|
|
96
|
+
</View>
|
|
97
|
+
)}
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const styles = StyleSheet.create({
|
|
103
|
+
container: {
|
|
104
|
+
gap: 8,
|
|
105
|
+
},
|
|
106
|
+
button: {
|
|
107
|
+
flexDirection: "row",
|
|
108
|
+
alignItems: "center",
|
|
109
|
+
justifyContent: "center",
|
|
110
|
+
paddingVertical: 12,
|
|
111
|
+
paddingHorizontal: 16,
|
|
112
|
+
backgroundColor: "#FF9500",
|
|
113
|
+
borderRadius: 8,
|
|
114
|
+
gap: 8,
|
|
115
|
+
},
|
|
116
|
+
buttonDisabled: {
|
|
117
|
+
opacity: 0.6,
|
|
118
|
+
},
|
|
119
|
+
buttonText: {
|
|
120
|
+
fontSize: 16,
|
|
121
|
+
fontWeight: "600",
|
|
122
|
+
color: "#FFFFFF",
|
|
123
|
+
},
|
|
124
|
+
spinner: {
|
|
125
|
+
marginRight: 4,
|
|
126
|
+
},
|
|
127
|
+
progressContainer: {
|
|
128
|
+
marginTop: 4,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
FlatList,
|
|
5
|
+
Pressable,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
View,
|
|
9
|
+
} from "react-native";
|
|
10
|
+
import { useGalleryUpload } from "../hooks";
|
|
11
|
+
import type { UseGalleryUploadOptions } from "../types";
|
|
12
|
+
import { UploadProgress } from "./UploadProgress";
|
|
13
|
+
|
|
14
|
+
export interface GalleryUploadButtonProps {
|
|
15
|
+
/** Options for gallery upload */
|
|
16
|
+
options?: UseGalleryUploadOptions;
|
|
17
|
+
/** Button label text */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** Custom button content */
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
/** Callback when all uploads complete successfully */
|
|
22
|
+
onSuccess?: (results: unknown[]) => void;
|
|
23
|
+
/** Callback when any upload fails */
|
|
24
|
+
onError?: (error: Error) => void;
|
|
25
|
+
/** Callback when upload is cancelled */
|
|
26
|
+
onCancel?: () => void;
|
|
27
|
+
/** Whether to show individual progress for each file */
|
|
28
|
+
showProgress?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Button component for gallery selection and batch upload
|
|
33
|
+
* Triggers gallery picker on press and handles concurrent uploads
|
|
34
|
+
*/
|
|
35
|
+
export function GalleryUploadButton({
|
|
36
|
+
options,
|
|
37
|
+
label = "Select from Gallery",
|
|
38
|
+
children,
|
|
39
|
+
onSuccess,
|
|
40
|
+
onError,
|
|
41
|
+
onCancel,
|
|
42
|
+
showProgress = true,
|
|
43
|
+
}: GalleryUploadButtonProps) {
|
|
44
|
+
const { state, selectAndUpload } = useGalleryUpload(options);
|
|
45
|
+
|
|
46
|
+
const handlePress = async () => {
|
|
47
|
+
try {
|
|
48
|
+
await selectAndUpload();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error instanceof Error) {
|
|
51
|
+
if (
|
|
52
|
+
error.message.includes("cancelled") ||
|
|
53
|
+
error.message.includes("aborted")
|
|
54
|
+
) {
|
|
55
|
+
onCancel?.();
|
|
56
|
+
} else {
|
|
57
|
+
onError?.(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const isLoading = state.items.some((item) => item.status === "uploading");
|
|
64
|
+
const hasItems = state.items.length > 0;
|
|
65
|
+
const allComplete =
|
|
66
|
+
hasItems &&
|
|
67
|
+
state.items.every(
|
|
68
|
+
(item) => item.status !== "uploading" && item.status !== "idle",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (allComplete) {
|
|
73
|
+
const results = state.items
|
|
74
|
+
.filter((item) => item.status === "success")
|
|
75
|
+
.map((item) => item.result);
|
|
76
|
+
if (results.length > 0) {
|
|
77
|
+
onSuccess?.(results);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, [allComplete, state.items, onSuccess]);
|
|
81
|
+
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
const errors = state.items.filter((item) => item.status === "error");
|
|
84
|
+
const firstError = errors[0]?.error;
|
|
85
|
+
if (firstError) {
|
|
86
|
+
onError?.(firstError);
|
|
87
|
+
}
|
|
88
|
+
}, [state.items, onError]);
|
|
89
|
+
|
|
90
|
+
const renderItem = ({ item }: { item: (typeof state.items)[0] }) => (
|
|
91
|
+
<View key={item.id} style={styles.itemContainer}>
|
|
92
|
+
<UploadProgress
|
|
93
|
+
state={{
|
|
94
|
+
status: item.status,
|
|
95
|
+
progress: item.progress,
|
|
96
|
+
bytesUploaded: item.bytesUploaded,
|
|
97
|
+
totalBytes: item.totalBytes,
|
|
98
|
+
error: item.error,
|
|
99
|
+
result: item.result,
|
|
100
|
+
}}
|
|
101
|
+
label={item.file.name}
|
|
102
|
+
/>
|
|
103
|
+
</View>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<View style={styles.container}>
|
|
108
|
+
<Pressable
|
|
109
|
+
style={[styles.button, isLoading && styles.buttonDisabled]}
|
|
110
|
+
onPress={handlePress}
|
|
111
|
+
disabled={isLoading}
|
|
112
|
+
>
|
|
113
|
+
{isLoading && (
|
|
114
|
+
<ActivityIndicator
|
|
115
|
+
size="small"
|
|
116
|
+
color="#FFFFFF"
|
|
117
|
+
style={styles.spinner}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
<Text style={styles.buttonText}>
|
|
121
|
+
{children || label}
|
|
122
|
+
{hasItems && ` (${state.items.length})`}
|
|
123
|
+
</Text>
|
|
124
|
+
</Pressable>
|
|
125
|
+
|
|
126
|
+
{hasItems && (
|
|
127
|
+
<View style={styles.statsContainer}>
|
|
128
|
+
<Text style={styles.statsText}>
|
|
129
|
+
Progress: {state.items.filter((i) => i.status === "success").length}
|
|
130
|
+
/{state.items.length} uploaded
|
|
131
|
+
</Text>
|
|
132
|
+
<Text style={styles.statsText}>Overall: {state.totalProgress}%</Text>
|
|
133
|
+
</View>
|
|
134
|
+
)}
|
|
135
|
+
|
|
136
|
+
{showProgress && hasItems && (
|
|
137
|
+
<FlatList
|
|
138
|
+
scrollEnabled={false}
|
|
139
|
+
data={state.items}
|
|
140
|
+
renderItem={renderItem}
|
|
141
|
+
keyExtractor={(item) => item.id}
|
|
142
|
+
style={styles.listContainer}
|
|
143
|
+
contentContainerStyle={styles.listContent}
|
|
144
|
+
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
</View>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const styles = StyleSheet.create({
|
|
152
|
+
container: {
|
|
153
|
+
gap: 8,
|
|
154
|
+
},
|
|
155
|
+
button: {
|
|
156
|
+
flexDirection: "row",
|
|
157
|
+
alignItems: "center",
|
|
158
|
+
justifyContent: "center",
|
|
159
|
+
paddingVertical: 12,
|
|
160
|
+
paddingHorizontal: 16,
|
|
161
|
+
backgroundColor: "#34C759",
|
|
162
|
+
borderRadius: 8,
|
|
163
|
+
gap: 8,
|
|
164
|
+
},
|
|
165
|
+
buttonDisabled: {
|
|
166
|
+
opacity: 0.6,
|
|
167
|
+
},
|
|
168
|
+
buttonText: {
|
|
169
|
+
fontSize: 16,
|
|
170
|
+
fontWeight: "600",
|
|
171
|
+
color: "#FFFFFF",
|
|
172
|
+
},
|
|
173
|
+
spinner: {
|
|
174
|
+
marginRight: 4,
|
|
175
|
+
},
|
|
176
|
+
statsContainer: {
|
|
177
|
+
paddingVertical: 8,
|
|
178
|
+
paddingHorizontal: 12,
|
|
179
|
+
backgroundColor: "#f5f5f5",
|
|
180
|
+
borderRadius: 4,
|
|
181
|
+
gap: 4,
|
|
182
|
+
},
|
|
183
|
+
statsText: {
|
|
184
|
+
fontSize: 12,
|
|
185
|
+
color: "#666666",
|
|
186
|
+
},
|
|
187
|
+
listContainer: {
|
|
188
|
+
maxHeight: 400,
|
|
189
|
+
},
|
|
190
|
+
listContent: {
|
|
191
|
+
gap: 8,
|
|
192
|
+
},
|
|
193
|
+
itemContainer: {
|
|
194
|
+
paddingHorizontal: 0,
|
|
195
|
+
},
|
|
196
|
+
separator: {
|
|
197
|
+
height: 4,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { FlatList, Pressable, StyleSheet, Text, View } from "react-native";
|
|
2
|
+
import type { UploadItem } from "../types";
|
|
3
|
+
import { UploadProgress } from "./UploadProgress";
|
|
4
|
+
|
|
5
|
+
export interface UploadListProps {
|
|
6
|
+
/** List of upload items to display */
|
|
7
|
+
items: UploadItem[];
|
|
8
|
+
/** Callback when remove item is pressed */
|
|
9
|
+
onRemove?: (id: string) => void;
|
|
10
|
+
/** Callback when item is pressed */
|
|
11
|
+
onItemPress?: (item: UploadItem) => void;
|
|
12
|
+
/** Whether to show remove button */
|
|
13
|
+
showRemoveButton?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Component to display a list of upload items with individual progress
|
|
18
|
+
* Shows status indicators and allows removal of items
|
|
19
|
+
*/
|
|
20
|
+
export function UploadList({
|
|
21
|
+
items,
|
|
22
|
+
onRemove,
|
|
23
|
+
onItemPress,
|
|
24
|
+
showRemoveButton = true,
|
|
25
|
+
}: UploadListProps) {
|
|
26
|
+
const renderItem = ({ item }: { item: UploadItem }) => (
|
|
27
|
+
<Pressable
|
|
28
|
+
style={[
|
|
29
|
+
styles.itemContainer,
|
|
30
|
+
{ borderLeftColor: getStatusColor(item.progress.state) },
|
|
31
|
+
]}
|
|
32
|
+
onPress={() => onItemPress?.(item)}
|
|
33
|
+
>
|
|
34
|
+
<View style={styles.itemContent}>
|
|
35
|
+
<View style={styles.itemHeader}>
|
|
36
|
+
<Text style={styles.fileName} numberOfLines={1}>
|
|
37
|
+
{item.file.name}
|
|
38
|
+
</Text>
|
|
39
|
+
<Text style={styles.fileSize}>
|
|
40
|
+
{getFileSizeDisplay(item.file.size)}
|
|
41
|
+
</Text>
|
|
42
|
+
</View>
|
|
43
|
+
<View style={styles.progressWrapper}>
|
|
44
|
+
<UploadProgress
|
|
45
|
+
state={{
|
|
46
|
+
status:
|
|
47
|
+
item.progress.state === "pending"
|
|
48
|
+
? "idle"
|
|
49
|
+
: item.progress.state === "cancelled"
|
|
50
|
+
? "aborted"
|
|
51
|
+
: item.progress.state,
|
|
52
|
+
progress: item.progress.progress,
|
|
53
|
+
bytesUploaded: item.progress.uploadedBytes,
|
|
54
|
+
totalBytes: item.progress.totalBytes,
|
|
55
|
+
error: item.progress.error || null,
|
|
56
|
+
result: (item.result as any) || null,
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
</View>
|
|
60
|
+
</View>
|
|
61
|
+
{showRemoveButton &&
|
|
62
|
+
item.progress.state !== "uploading" &&
|
|
63
|
+
item.progress.state !== "pending" && (
|
|
64
|
+
<Pressable
|
|
65
|
+
style={styles.removeButton}
|
|
66
|
+
onPress={() => onRemove?.(item.id)}
|
|
67
|
+
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
|
|
68
|
+
>
|
|
69
|
+
<Text style={styles.removeButtonText}>✕</Text>
|
|
70
|
+
</Pressable>
|
|
71
|
+
)}
|
|
72
|
+
</Pressable>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (items.length === 0) {
|
|
76
|
+
return (
|
|
77
|
+
<View style={styles.emptyContainer}>
|
|
78
|
+
<Text style={styles.emptyText}>No uploads</Text>
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<View style={styles.container}>
|
|
85
|
+
<View style={styles.headerRow}>
|
|
86
|
+
<Text style={styles.headerText}>Uploads ({items.length})</Text>
|
|
87
|
+
<Text style={styles.headerSubtext}>
|
|
88
|
+
{items.filter((i) => i.progress.state === "success").length} complete
|
|
89
|
+
</Text>
|
|
90
|
+
</View>
|
|
91
|
+
<FlatList
|
|
92
|
+
scrollEnabled={false}
|
|
93
|
+
data={items}
|
|
94
|
+
renderItem={renderItem}
|
|
95
|
+
keyExtractor={(item) => item.id}
|
|
96
|
+
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
|
97
|
+
contentContainerStyle={styles.listContent}
|
|
98
|
+
/>
|
|
99
|
+
</View>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Helper functions
|
|
104
|
+
function getStatusColor(state: string): string {
|
|
105
|
+
switch (state) {
|
|
106
|
+
case "success":
|
|
107
|
+
return "#34C759";
|
|
108
|
+
case "error":
|
|
109
|
+
case "cancelled":
|
|
110
|
+
return "#FF3B30";
|
|
111
|
+
case "uploading":
|
|
112
|
+
case "pending":
|
|
113
|
+
return "#007AFF";
|
|
114
|
+
default:
|
|
115
|
+
return "#999999";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getFileSizeDisplay(bytes: number): string {
|
|
120
|
+
if (bytes === 0) return "0 B";
|
|
121
|
+
const k = 1024;
|
|
122
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
123
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
124
|
+
return `${Math.round((bytes / k ** i) * 10) / 10} ${sizes[i]}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const styles = StyleSheet.create({
|
|
128
|
+
container: {
|
|
129
|
+
gap: 8,
|
|
130
|
+
},
|
|
131
|
+
headerRow: {
|
|
132
|
+
flexDirection: "row",
|
|
133
|
+
justifyContent: "space-between",
|
|
134
|
+
alignItems: "center",
|
|
135
|
+
paddingHorizontal: 12,
|
|
136
|
+
paddingVertical: 8,
|
|
137
|
+
backgroundColor: "#f9f9f9",
|
|
138
|
+
borderRadius: 4,
|
|
139
|
+
},
|
|
140
|
+
headerText: {
|
|
141
|
+
fontSize: 16,
|
|
142
|
+
fontWeight: "600",
|
|
143
|
+
color: "#333333",
|
|
144
|
+
},
|
|
145
|
+
headerSubtext: {
|
|
146
|
+
fontSize: 14,
|
|
147
|
+
color: "#666666",
|
|
148
|
+
},
|
|
149
|
+
listContent: {
|
|
150
|
+
gap: 8,
|
|
151
|
+
},
|
|
152
|
+
itemContainer: {
|
|
153
|
+
flexDirection: "row",
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
paddingVertical: 8,
|
|
156
|
+
paddingHorizontal: 12,
|
|
157
|
+
borderLeftWidth: 4,
|
|
158
|
+
backgroundColor: "#f5f5f5",
|
|
159
|
+
borderRadius: 4,
|
|
160
|
+
gap: 8,
|
|
161
|
+
},
|
|
162
|
+
itemContent: {
|
|
163
|
+
flex: 1,
|
|
164
|
+
gap: 6,
|
|
165
|
+
},
|
|
166
|
+
itemHeader: {
|
|
167
|
+
flexDirection: "row",
|
|
168
|
+
justifyContent: "space-between",
|
|
169
|
+
alignItems: "center",
|
|
170
|
+
},
|
|
171
|
+
fileName: {
|
|
172
|
+
fontSize: 14,
|
|
173
|
+
fontWeight: "500",
|
|
174
|
+
color: "#333333",
|
|
175
|
+
flex: 1,
|
|
176
|
+
},
|
|
177
|
+
fileSize: {
|
|
178
|
+
fontSize: 12,
|
|
179
|
+
color: "#999999",
|
|
180
|
+
marginLeft: 8,
|
|
181
|
+
},
|
|
182
|
+
progressWrapper: {
|
|
183
|
+
marginTop: 2,
|
|
184
|
+
},
|
|
185
|
+
removeButton: {
|
|
186
|
+
width: 32,
|
|
187
|
+
height: 32,
|
|
188
|
+
justifyContent: "center",
|
|
189
|
+
alignItems: "center",
|
|
190
|
+
borderRadius: 16,
|
|
191
|
+
backgroundColor: "#FFE5E5",
|
|
192
|
+
},
|
|
193
|
+
removeButtonText: {
|
|
194
|
+
fontSize: 16,
|
|
195
|
+
fontWeight: "600",
|
|
196
|
+
color: "#FF3B30",
|
|
197
|
+
},
|
|
198
|
+
separator: {
|
|
199
|
+
height: 4,
|
|
200
|
+
},
|
|
201
|
+
emptyContainer: {
|
|
202
|
+
paddingVertical: 24,
|
|
203
|
+
paddingHorizontal: 12,
|
|
204
|
+
backgroundColor: "#f5f5f5",
|
|
205
|
+
borderRadius: 4,
|
|
206
|
+
alignItems: "center",
|
|
207
|
+
justifyContent: "center",
|
|
208
|
+
},
|
|
209
|
+
emptyText: {
|
|
210
|
+
fontSize: 14,
|
|
211
|
+
color: "#999999",
|
|
212
|
+
fontStyle: "italic",
|
|
213
|
+
},
|
|
214
|
+
});
|