paseo-form-render 0.0.0-stage → 0.2.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.
@@ -0,0 +1,690 @@
1
+ import type { PluginHostProps } from "@getpaseo/plugin/client";
2
+ import { useTranslation } from "@getpaseo/plugin/client";
3
+ import React, { useEffect, useMemo, useState } from "react";
4
+ import {
5
+ Modal,
6
+ Pressable,
7
+ ScrollView,
8
+ StyleSheet,
9
+ Text,
10
+ TextInput,
11
+ View,
12
+ } from "react-native";
13
+ import {
14
+ listProjectFilesRpc,
15
+ createDirectoryRpc,
16
+ type ProjectFileEntry,
17
+ type FilePickerShortcut,
18
+ } from "../shared/pickers";
19
+ import { normalizeExtensions } from "../shared/schema";
20
+ import { messages } from "./messages";
21
+ import { usePfrRpc } from "./rpc-bridge";
22
+
23
+ interface ProjectFileModalProps {
24
+ visible: boolean;
25
+ onClose: () => void;
26
+ onSelect: (selectedPath: string) => void;
27
+ agentId?: string;
28
+ pickerType?: "file" | "folder";
29
+ extensions?: string[] | string;
30
+ extension?: string;
31
+ initialPath?: string;
32
+ theme: PluginHostProps["theme"];
33
+ }
34
+
35
+ export function ProjectFileModal({
36
+ visible,
37
+ onClose,
38
+ onSelect,
39
+ agentId,
40
+ pickerType = "file",
41
+ extensions,
42
+ extension,
43
+ initialPath,
44
+ theme,
45
+ }: ProjectFileModalProps) {
46
+ const listProjectFiles = usePfrRpc(listProjectFilesRpc);
47
+ const createDir = usePfrRpc(createDirectoryRpc);
48
+ const { t } = useTranslation(messages);
49
+ const [currentPath, setCurrentPath] = useState("");
50
+ const [parentPath, setParentPath] = useState<string | null>(null);
51
+ const [entries, setEntries] = useState<ProjectFileEntry[]>([]);
52
+ const [shortcuts, setShortcuts] = useState<FilePickerShortcut[]>([]);
53
+ const [isWorkspaceRelative, setIsWorkspaceRelative] = useState(true);
54
+ const [loading, setLoading] = useState(false);
55
+ const [error, setError] = useState<string | null>(null);
56
+ const [search, setSearch] = useState("");
57
+ const [selectedPath, setSelectedPath] = useState<string>("");
58
+ const [isCreatingFolder, setIsCreatingFolder] = useState(false);
59
+ const [newFolderName, setNewFolderName] = useState("");
60
+ const [creatingFolderLoading, setCreatingFolderLoading] = useState(false);
61
+ const [newFolderError, setNewFolderError] = useState<string | null>(null);
62
+
63
+ const cleanExts = useMemo(
64
+ () => (pickerType === "folder" ? undefined : normalizeExtensions(extensions, extension)),
65
+ [pickerType, extensions, extension],
66
+ );
67
+
68
+ useEffect(() => {
69
+ if (!visible) return;
70
+ const initPath = initialPath?.trim() || "";
71
+ const initDir =
72
+ initPath.includes("/") || initPath.includes("\\")
73
+ ? initPath.replace(/[/\\][^/\\]*$/, "")
74
+ : "";
75
+ setSelectedPath(initPath);
76
+ loadDirectory(initDir);
77
+ }, [visible, initialPath]);
78
+
79
+ async function loadDirectory(targetPath: string) {
80
+ if (!agentId) return;
81
+ setLoading(true);
82
+ setError(null);
83
+ setSearch("");
84
+ setIsCreatingFolder(false);
85
+ setNewFolderError(null);
86
+ try {
87
+ const res = await listProjectFiles({ agentId, targetPath });
88
+ if (res.parentPath !== undefined) {
89
+ setParentPath(res.parentPath);
90
+ }
91
+ if (res.currentPath !== undefined) {
92
+ setCurrentPath(res.currentPath);
93
+ }
94
+ if (res.shortcuts) {
95
+ setShortcuts(res.shortcuts);
96
+ }
97
+ if (res.isWorkspaceRelative !== undefined) {
98
+ setIsWorkspaceRelative(res.isWorkspaceRelative);
99
+ }
100
+
101
+ if (res.error) {
102
+ setError(res.error);
103
+ setEntries([]);
104
+ } else {
105
+ setEntries(res.entries);
106
+ }
107
+ } catch (err) {
108
+ setError(err instanceof Error ? err.message : String(err));
109
+ } finally {
110
+ setLoading(false);
111
+ }
112
+ }
113
+
114
+ const handleCreateFolderSubmit = async () => {
115
+ if (!agentId || !newFolderName.trim() || creatingFolderLoading) return;
116
+ const name = newFolderName.trim();
117
+ if (/[<>:"/\\|?*]/.test(name) || name === "." || name === "..") {
118
+ setNewFolderError(t("picker.invalidFolderName"));
119
+ return;
120
+ }
121
+ setCreatingFolderLoading(true);
122
+ setNewFolderError(null);
123
+ try {
124
+ const res = await createDir({
125
+ agentId,
126
+ parentPath: currentPath || "",
127
+ name,
128
+ });
129
+ if (res.success && res.fullPath) {
130
+ setIsCreatingFolder(false);
131
+ setNewFolderName("");
132
+ setNewFolderError(null);
133
+ await loadDirectory(res.fullPath);
134
+ if (pickerType === "folder") {
135
+ setSelectedPath(res.relativePath || res.fullPath);
136
+ }
137
+ } else {
138
+ setNewFolderError(res.error || t("picker.createFailed"));
139
+ }
140
+ } catch (err) {
141
+ setNewFolderError(err instanceof Error ? err.message : String(err));
142
+ } finally {
143
+ setCreatingFolderLoading(false);
144
+ }
145
+ };
146
+
147
+ const filteredEntries = useMemo(() => {
148
+ let result = entries;
149
+ if (search.trim()) {
150
+ const query = search.trim().toLowerCase();
151
+ result = result.filter((e) => e.name.toLowerCase().includes(query));
152
+ }
153
+ if (cleanExts && cleanExts.length > 0) {
154
+ result = result.filter((e) => {
155
+ if (e.isDirectory) return true;
156
+ const nameLower = e.name.toLowerCase();
157
+ return cleanExts.some((ext) => nameLower.endsWith("." + ext));
158
+ });
159
+ }
160
+ return result;
161
+ }, [entries, search, cleanExts]);
162
+
163
+ const breadcrumbs = useMemo(() => {
164
+ if (isWorkspaceRelative) {
165
+ if (!currentPath) return [{ label: t("picker.projectRoot"), path: "" }];
166
+ const parts = currentPath.split("/").filter(Boolean);
167
+ const crumbs = [{ label: t("picker.project"), path: "" }];
168
+ let accum = "";
169
+ for (const part of parts) {
170
+ accum = accum ? `${accum}/${part}` : part;
171
+ crumbs.push({ label: part, path: accum });
172
+ }
173
+ return crumbs;
174
+ } else {
175
+ const isWindows = /^[a-zA-Z]:/.test(currentPath);
176
+ const parts = currentPath.split(/[/\\]/).filter(Boolean);
177
+ const crumbs: { label: string; path: string }[] = [];
178
+ if (isWindows) {
179
+ const drive = parts[0];
180
+ crumbs.push({ label: drive, path: drive + "\\" });
181
+ let accum = drive + "\\";
182
+ for (let i = 1; i < parts.length; i++) {
183
+ accum = accum + (accum.endsWith("\\") ? "" : "\\") + parts[i];
184
+ crumbs.push({ label: parts[i], path: accum });
185
+ }
186
+ } else {
187
+ crumbs.push({ label: "/", path: "/" });
188
+ let accum = "";
189
+ for (const part of parts) {
190
+ accum = `${accum}/${part}`;
191
+ crumbs.push({ label: part, path: accum });
192
+ }
193
+ }
194
+ return crumbs;
195
+ }
196
+ }, [currentPath, isWorkspaceRelative, t]);
197
+
198
+ const handleEntryPress = (entry: ProjectFileEntry) => {
199
+ if (entry.isDirectory) {
200
+ loadDirectory(entry.fullPath || entry.relativePath);
201
+ if (pickerType === "folder") {
202
+ setSelectedPath(entry.relativePath);
203
+ }
204
+ } else {
205
+ if (pickerType !== "folder") {
206
+ setSelectedPath(entry.relativePath);
207
+ }
208
+ }
209
+ };
210
+
211
+ const effectiveSelected =
212
+ selectedPath || (pickerType === "folder" ? currentPath || "." : "");
213
+
214
+ const handleConfirm = () => {
215
+ if (effectiveSelected) {
216
+ onSelect(effectiveSelected);
217
+ onClose();
218
+ }
219
+ };
220
+
221
+ const colors = theme.colors;
222
+
223
+ if (!visible) return null;
224
+
225
+ return (
226
+ <Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
227
+ <View style={styles.backdrop}>
228
+ <View style={[styles.dialog, { backgroundColor: colors.surface0, borderColor: colors.border }]}>
229
+ {/* Header */}
230
+ <View style={[styles.header, { borderBottomColor: colors.border }]}>
231
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8, flex: 1, marginRight: 8 }}>
232
+ <Text style={[styles.title, { color: colors.foreground }]}>
233
+ {pickerType === "folder" ? t("picker.selectFolder") : t("picker.selectFile")}
234
+ </Text>
235
+ {cleanExts && cleanExts.length > 0 && (
236
+ <View style={[styles.filterBadge, { backgroundColor: colors.surface1, borderColor: colors.border }]}>
237
+ <Text style={[styles.filterBadgeText, { color: colors.accent }]}>
238
+ {cleanExts.map((e) => `.${e}`).join(", ")}
239
+ </Text>
240
+ </View>
241
+ )}
242
+ </View>
243
+ <Pressable onPress={onClose} style={styles.closeBtn}>
244
+ <Text style={{ color: colors.foregroundMuted, fontSize: 18, fontWeight: "600" }}>✕</Text>
245
+ </Pressable>
246
+ </View>
247
+
248
+ {/* Quick Shortcuts Bar */}
249
+ {shortcuts.length > 0 && (
250
+ <View style={[styles.shortcutsBar, { borderBottomColor: colors.border, backgroundColor: colors.surface1 }]}>
251
+ <Text style={[styles.shortcutsLabel, { color: colors.foregroundMuted }]}>{t("picker.quick")}</Text>
252
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.shortcutsList}>
253
+ {shortcuts.map((sc) => (
254
+ <Pressable
255
+ key={sc.id}
256
+ onPress={() => loadDirectory(sc.path)}
257
+ style={[
258
+ styles.shortcutPill,
259
+ {
260
+ backgroundColor: colors.surface2,
261
+ borderColor: colors.border,
262
+ },
263
+ ]}
264
+ >
265
+ <Text style={styles.shortcutIcon}>{sc.icon}</Text>
266
+ <Text style={[styles.shortcutText, { color: colors.foreground }]}>{sc.label}</Text>
267
+ </Pressable>
268
+ ))}
269
+ </ScrollView>
270
+ </View>
271
+ )}
272
+
273
+ {/* Breadcrumbs */}
274
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.breadcrumbBar}>
275
+ {breadcrumbs.map((crumb, idx) => (
276
+ <React.Fragment key={crumb.path}>
277
+ {idx > 0 && <Text style={{ color: colors.foregroundMuted, marginHorizontal: 4 }}>/</Text>}
278
+ <Pressable onPress={() => loadDirectory(crumb.path)}>
279
+ <Text
280
+ style={{
281
+ color: idx === breadcrumbs.length - 1 ? colors.foreground : colors.accent,
282
+ fontWeight: idx === breadcrumbs.length - 1 ? "600" : "normal",
283
+ fontSize: 13,
284
+ }}
285
+ >
286
+ {crumb.label}
287
+ </Text>
288
+ </Pressable>
289
+ </React.Fragment>
290
+ ))}
291
+ </ScrollView>
292
+
293
+ {/* Search Bar & Actions */}
294
+ <View style={styles.searchContainer}>
295
+ <TextInput
296
+ style={[
297
+ styles.searchInput,
298
+ {
299
+ backgroundColor: colors.surface1,
300
+ color: colors.foreground,
301
+ borderColor: colors.border,
302
+ },
303
+ ]}
304
+ placeholder={t("picker.searchPlaceholder")}
305
+ placeholderTextColor={colors.foregroundMuted}
306
+ value={search}
307
+ onChangeText={setSearch}
308
+ />
309
+ {pickerType === "folder" && (
310
+ <Pressable
311
+ onPress={() => {
312
+ setIsCreatingFolder((prev) => !prev);
313
+ setNewFolderName("");
314
+ setNewFolderError(null);
315
+ }}
316
+ style={[
317
+ styles.newFolderBtn,
318
+ {
319
+ backgroundColor: isCreatingFolder ? colors.surface2 : colors.surface1,
320
+ borderColor: isCreatingFolder ? colors.accent : colors.border,
321
+ },
322
+ ]}
323
+ >
324
+ <Text
325
+ style={{
326
+ color: isCreatingFolder ? colors.accent : colors.foreground,
327
+ fontSize: 12,
328
+ fontWeight: "600",
329
+ }}
330
+ >
331
+ 📁+ New Folder
332
+ </Text>
333
+ </Pressable>
334
+ )}
335
+ </View>
336
+
337
+ {/* Inline New Folder Creation Row */}
338
+ {isCreatingFolder && (
339
+ <View style={{ marginHorizontal: 16, marginBottom: 8 }}>
340
+ <View
341
+ style={[
342
+ styles.newFolderRow,
343
+ {
344
+ backgroundColor: colors.surface1,
345
+ borderColor: newFolderError ? colors.statusDanger : colors.accent,
346
+ },
347
+ ]}
348
+ >
349
+ <Text style={styles.entryIcon}>📁</Text>
350
+ <TextInput
351
+ style={[
352
+ styles.newFolderInput,
353
+ {
354
+ color: colors.foreground,
355
+ backgroundColor: colors.surface0,
356
+ borderColor: colors.border,
357
+ },
358
+ ]}
359
+ placeholder={t("picker.newFolderPlaceholder")}
360
+ placeholderTextColor={colors.foregroundMuted}
361
+ value={newFolderName}
362
+ onChangeText={(t) => {
363
+ setNewFolderName(t);
364
+ if (newFolderError) setNewFolderError(null);
365
+ }}
366
+ autoFocus
367
+ onSubmitEditing={handleCreateFolderSubmit}
368
+ />
369
+ <Pressable
370
+ disabled={creatingFolderLoading || !newFolderName.trim()}
371
+ onPress={handleCreateFolderSubmit}
372
+ style={[
373
+ styles.newFolderActionBtn,
374
+ {
375
+ backgroundColor: colors.accent,
376
+ opacity: creatingFolderLoading || !newFolderName.trim() ? 0.6 : 1,
377
+ },
378
+ ]}
379
+ >
380
+ <Text style={{ color: colors.surface0, fontSize: 12, fontWeight: "600" }}>
381
+ {creatingFolderLoading ? "..." : t("picker.create")}
382
+ </Text>
383
+ </Pressable>
384
+ <Pressable
385
+ onPress={() => {
386
+ setIsCreatingFolder(false);
387
+ setNewFolderName("");
388
+ setNewFolderError(null);
389
+ }}
390
+ style={[styles.newFolderActionBtn, { backgroundColor: colors.surface2 }]}
391
+ >
392
+ <Text style={{ color: colors.foregroundMuted, fontSize: 12, fontWeight: "600" }}>
393
+ Cancel
394
+ </Text>
395
+ </Pressable>
396
+ </View>
397
+ {newFolderError && (
398
+ <Text style={{ color: colors.statusDanger, fontSize: 12, marginTop: 4 }}>
399
+ {newFolderError}
400
+ </Text>
401
+ )}
402
+ </View>
403
+ )}
404
+
405
+ {/* Directory Content List */}
406
+ <ScrollView style={styles.listContainer}>
407
+ {parentPath !== null && (
408
+ <Pressable
409
+ style={[styles.entryRow, { borderBottomColor: colors.surface2 }]}
410
+ onPress={() => loadDirectory(parentPath)}
411
+ >
412
+ <Text style={styles.entryIcon}>📁</Text>
413
+ <Text style={{ color: colors.foregroundMuted, fontSize: 14 }}>{t("picker.upToParent")}</Text>
414
+ </Pressable>
415
+ )}
416
+
417
+ {loading ? (
418
+ <Text style={[styles.emptyText, { color: colors.foregroundMuted }]}>{t("picker.loading")}</Text>
419
+ ) : error ? (
420
+ <Text style={[styles.emptyText, { color: colors.statusDanger }]}>{error}</Text>
421
+ ) : filteredEntries.length === 0 ? (
422
+ <Text style={[styles.emptyText, { color: colors.foregroundMuted }]}>{t("picker.noMatches")}</Text>
423
+ ) : (
424
+ filteredEntries.map((entry) => {
425
+ const isSelected = selectedPath === entry.relativePath;
426
+ return (
427
+ <Pressable
428
+ key={entry.relativePath}
429
+ style={[
430
+ styles.entryRow,
431
+ {
432
+ backgroundColor: isSelected ? colors.surface2 : "transparent",
433
+ borderBottomColor: colors.surface1,
434
+ },
435
+ ]}
436
+ onPress={() => handleEntryPress(entry)}
437
+ >
438
+ <Text style={styles.entryIcon}>{entry.isDirectory ? "📁" : "📄"}</Text>
439
+ <Text
440
+ style={[
441
+ styles.entryName,
442
+ {
443
+ color: colors.foreground,
444
+ fontWeight: entry.isDirectory ? "600" : "normal",
445
+ },
446
+ ]}
447
+ >
448
+ {entry.name}
449
+ </Text>
450
+ {isSelected && (
451
+ <Text style={{ color: colors.accent, fontWeight: "600", fontSize: 13 }}>{t("picker.selected")}</Text>
452
+ )}
453
+ </Pressable>
454
+ );
455
+ })
456
+ )}
457
+ </ScrollView>
458
+
459
+ {/* Footer */}
460
+ <View style={[styles.footer, { borderTopColor: colors.border }]}>
461
+ <View style={{ flex: 1, marginRight: 10, overflow: "hidden" }}>
462
+ <Text style={{ color: colors.foregroundMuted, fontSize: 12 }}>{t("picker.selectedLabel")}</Text>
463
+ {effectiveSelected ? (
464
+ <ScrollView
465
+ horizontal
466
+ showsHorizontalScrollIndicator={false}
467
+ contentContainerStyle={{ alignItems: "center" }}
468
+ style={{ marginTop: 2 }}
469
+ >
470
+ <Text
471
+ style={{ color: colors.foreground, fontSize: 13, fontWeight: "500" }}
472
+ >
473
+ {effectiveSelected}
474
+ </Text>
475
+ </ScrollView>
476
+ ) : (
477
+ <Text
478
+ style={{
479
+ color: colors.foregroundMuted,
480
+ fontSize: 13,
481
+ fontStyle: "italic",
482
+ marginTop: 2,
483
+ }}
484
+ >
485
+ (none selected)
486
+ </Text>
487
+ )}
488
+ </View>
489
+
490
+ <View style={{ flexDirection: "row", gap: 8 }}>
491
+ <Pressable
492
+ onPress={onClose}
493
+ style={[styles.actionBtn, { borderColor: colors.border, borderWidth: 1 }]}
494
+ >
495
+ <Text style={{ color: colors.foreground, fontSize: 13 }}>{t("picker.cancel")}</Text>
496
+ </Pressable>
497
+
498
+ <Pressable
499
+ onPress={handleConfirm}
500
+ disabled={!effectiveSelected}
501
+ style={[
502
+ styles.actionBtn,
503
+ {
504
+ backgroundColor: effectiveSelected ? colors.accent : colors.surface2,
505
+ opacity: effectiveSelected ? 1 : 0.6,
506
+ },
507
+ ]}
508
+ >
509
+ <Text style={{ color: "#fff", fontWeight: "600", fontSize: 13 }}>
510
+ {pickerType === "folder" ? t("picker.chooseFolder") : t("picker.chooseFile")}
511
+ </Text>
512
+ </Pressable>
513
+ </View>
514
+ </View>
515
+ </View>
516
+ </View>
517
+ </Modal>
518
+ );
519
+ }
520
+
521
+ const styles = StyleSheet.create({
522
+ backdrop: {
523
+ flex: 1,
524
+ backgroundColor: "rgba(0, 0, 0, 0.55)",
525
+ justifyContent: "center",
526
+ alignItems: "center",
527
+ padding: 20,
528
+ },
529
+ dialog: {
530
+ width: "100%",
531
+ maxWidth: 720,
532
+ maxHeight: "85%",
533
+ borderRadius: 12,
534
+ borderWidth: 1,
535
+ overflow: "hidden",
536
+ display: "flex",
537
+ flexDirection: "column",
538
+ },
539
+ header: {
540
+ flexDirection: "row",
541
+ justifyContent: "space-between",
542
+ alignItems: "center",
543
+ paddingHorizontal: 16,
544
+ paddingVertical: 14,
545
+ borderBottomWidth: 1,
546
+ },
547
+ title: {
548
+ fontSize: 16,
549
+ fontWeight: "600",
550
+ },
551
+ filterBadge: {
552
+ paddingHorizontal: 7,
553
+ paddingVertical: 2,
554
+ borderRadius: 4,
555
+ borderWidth: 1,
556
+ },
557
+ filterBadgeText: {
558
+ fontSize: 11,
559
+ fontWeight: "600",
560
+ },
561
+ closeBtn: {
562
+ padding: 4,
563
+ },
564
+ shortcutsBar: {
565
+ flexDirection: "row",
566
+ alignItems: "center",
567
+ paddingHorizontal: 16,
568
+ paddingVertical: 8,
569
+ borderBottomWidth: 1,
570
+ gap: 8,
571
+ },
572
+ shortcutsLabel: {
573
+ fontSize: 12,
574
+ fontWeight: "600",
575
+ },
576
+ shortcutsList: {
577
+ flexDirection: "row",
578
+ alignItems: "center",
579
+ gap: 6,
580
+ },
581
+ shortcutPill: {
582
+ flexDirection: "row",
583
+ alignItems: "center",
584
+ paddingHorizontal: 8,
585
+ paddingVertical: 4,
586
+ borderRadius: 6,
587
+ borderWidth: 1,
588
+ gap: 4,
589
+ },
590
+ shortcutIcon: {
591
+ fontSize: 13,
592
+ },
593
+ shortcutText: {
594
+ fontSize: 12,
595
+ fontWeight: "500",
596
+ },
597
+ breadcrumbBar: {
598
+ flexDirection: "row",
599
+ paddingHorizontal: 16,
600
+ paddingVertical: 10,
601
+ maxHeight: 40,
602
+ },
603
+ searchContainer: {
604
+ flexDirection: "row",
605
+ alignItems: "center",
606
+ paddingHorizontal: 16,
607
+ paddingBottom: 10,
608
+ gap: 8,
609
+ },
610
+ searchInput: {
611
+ flex: 1,
612
+ borderWidth: 1,
613
+ borderRadius: 6,
614
+ paddingHorizontal: 10,
615
+ paddingVertical: 6,
616
+ fontSize: 13,
617
+ },
618
+ newFolderBtn: {
619
+ flexDirection: "row",
620
+ alignItems: "center",
621
+ gap: 4,
622
+ borderWidth: 1,
623
+ borderRadius: 6,
624
+ paddingHorizontal: 10,
625
+ paddingVertical: 6,
626
+ },
627
+ newFolderRow: {
628
+ flexDirection: "row",
629
+ alignItems: "center",
630
+ paddingHorizontal: 10,
631
+ paddingVertical: 7,
632
+ borderWidth: 1,
633
+ borderRadius: 6,
634
+ gap: 8,
635
+ },
636
+ newFolderInput: {
637
+ flex: 1,
638
+ borderWidth: 1,
639
+ borderRadius: 4,
640
+ paddingHorizontal: 8,
641
+ paddingVertical: 4,
642
+ fontSize: 13,
643
+ },
644
+ newFolderActionBtn: {
645
+ paddingHorizontal: 10,
646
+ paddingVertical: 6,
647
+ borderRadius: 4,
648
+ },
649
+ listContainer: {
650
+ flex: 1,
651
+ minHeight: 220,
652
+ paddingHorizontal: 16,
653
+ },
654
+ entryRow: {
655
+ flexDirection: "row",
656
+ alignItems: "center",
657
+ paddingVertical: 9,
658
+ paddingHorizontal: 8,
659
+ borderRadius: 6,
660
+ borderBottomWidth: StyleSheet.hairlineWidth,
661
+ },
662
+ entryIcon: {
663
+ fontSize: 16,
664
+ marginRight: 10,
665
+ },
666
+ entryName: {
667
+ fontSize: 14,
668
+ flex: 1,
669
+ },
670
+ emptyText: {
671
+ textAlign: "center",
672
+ paddingVertical: 32,
673
+ fontSize: 13,
674
+ },
675
+ footer: {
676
+ flexDirection: "row",
677
+ alignItems: "center",
678
+ justifyContent: "space-between",
679
+ paddingHorizontal: 16,
680
+ paddingVertical: 12,
681
+ borderTopWidth: 1,
682
+ },
683
+ actionBtn: {
684
+ paddingHorizontal: 14,
685
+ paddingVertical: 8,
686
+ borderRadius: 6,
687
+ alignItems: "center",
688
+ justifyContent: "center",
689
+ },
690
+ });
@@ -0,0 +1,10 @@
1
+ import "react-native";
2
+
3
+ declare module "react-native" {
4
+ interface ViewProps {
5
+ dataSet?: Record<string, string | number | boolean | undefined>;
6
+ }
7
+ interface TextProps {
8
+ dataSet?: Record<string, string | number | boolean | undefined>;
9
+ }
10
+ }