@nextsparkjs/mobile 0.1.0-beta.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.
- package/README.md +339 -0
- package/dist/api/client.d.ts +102 -0
- package/dist/api/client.js +189 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/client.types.d.ts +39 -0
- package/dist/api/client.types.js +12 -0
- package/dist/api/client.types.js.map +1 -0
- package/dist/api/core/auth.d.ts +26 -0
- package/dist/api/core/auth.js +52 -0
- package/dist/api/core/auth.js.map +1 -0
- package/dist/api/core/index.d.ts +4 -0
- package/dist/api/core/index.js +5 -0
- package/dist/api/core/index.js.map +1 -0
- package/dist/api/core/teams.d.ts +20 -0
- package/dist/api/core/teams.js +19 -0
- package/dist/api/core/teams.js.map +1 -0
- package/dist/api/core/types.d.ts +58 -0
- package/dist/api/core/types.js +1 -0
- package/dist/api/core/types.js.map +1 -0
- package/dist/api/core/users.d.ts +43 -0
- package/dist/api/core/users.js +41 -0
- package/dist/api/core/users.js.map +1 -0
- package/dist/api/entities/factory.d.ts +43 -0
- package/dist/api/entities/factory.js +31 -0
- package/dist/api/entities/factory.js.map +1 -0
- package/dist/api/entities/index.d.ts +3 -0
- package/dist/api/entities/index.js +3 -0
- package/dist/api/entities/index.js.map +1 -0
- package/dist/api/entities/types.d.ts +32 -0
- package/dist/api/entities/types.js +1 -0
- package/dist/api/entities/types.js.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.js +15 -0
- package/dist/api/index.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/alert.d.ts +34 -0
- package/dist/lib/alert.js +73 -0
- package/dist/lib/alert.js.map +1 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/storage.d.ts +1 -0
- package/dist/lib/storage.js +29 -0
- package/dist/lib/storage.js.map +1 -0
- package/dist/providers/AuthProvider.d.ts +21 -0
- package/dist/providers/AuthProvider.js +113 -0
- package/dist/providers/AuthProvider.js.map +1 -0
- package/dist/providers/QueryProvider.d.ts +11 -0
- package/dist/providers/QueryProvider.js +23 -0
- package/dist/providers/QueryProvider.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/storage-BaRppHUz.d.ts +22 -0
- package/package.json +99 -0
- package/templates/app/(app)/_layout.tsx +216 -0
- package/templates/app/(app)/customer/[id].tsx +68 -0
- package/templates/app/(app)/customer/create.tsx +24 -0
- package/templates/app/(app)/customers.tsx +164 -0
- package/templates/app/(app)/index.tsx +310 -0
- package/templates/app/(app)/notifications.tsx +242 -0
- package/templates/app/(app)/profile.tsx +254 -0
- package/templates/app/(app)/settings.tsx +241 -0
- package/templates/app/(app)/task/[id].tsx +70 -0
- package/templates/app/(app)/task/create.tsx +24 -0
- package/templates/app/(app)/tasks.tsx +164 -0
- package/templates/app/_layout.tsx +54 -0
- package/templates/app/index.tsx +35 -0
- package/templates/app/login.tsx +179 -0
- package/templates/app.config.ts +39 -0
- package/templates/babel.config.js +9 -0
- package/templates/eas.json +18 -0
- package/templates/jest.config.js +12 -0
- package/templates/metro.config.js +23 -0
- package/templates/package.json.template +52 -0
- package/templates/src/components/entities/customers/CustomerCard.tsx +59 -0
- package/templates/src/components/entities/customers/CustomerForm.tsx +194 -0
- package/templates/src/components/entities/customers/index.ts +6 -0
- package/templates/src/components/entities/index.ts +9 -0
- package/templates/src/components/entities/tasks/TaskCard.tsx +89 -0
- package/templates/src/components/entities/tasks/TaskForm.tsx +231 -0
- package/templates/src/components/entities/tasks/index.ts +6 -0
- package/templates/src/components/features/index.ts +6 -0
- package/templates/src/components/navigation/BottomTabBar.tsx +80 -0
- package/templates/src/components/navigation/CreateSheet.tsx +108 -0
- package/templates/src/components/navigation/MoreSheet.tsx +403 -0
- package/templates/src/components/navigation/TopBar.tsx +74 -0
- package/templates/src/components/navigation/index.ts +8 -0
- package/templates/src/components/ui/index.ts +89 -0
- package/templates/src/components/ui/text.tsx +64 -0
- package/templates/src/config/api.config.ts +26 -0
- package/templates/src/config/app.config.ts +15 -0
- package/templates/src/config/hooks.ts +58 -0
- package/templates/src/config/permissions.config.ts +119 -0
- package/templates/src/constants/colors.ts +55 -0
- package/templates/src/data/notifications.mock.json +100 -0
- package/templates/src/entities/customers/api.ts +10 -0
- package/templates/src/entities/customers/constants.internal.ts +6 -0
- package/templates/src/entities/customers/constants.ts +14 -0
- package/templates/src/entities/customers/index.ts +9 -0
- package/templates/src/entities/customers/mutations.ts +58 -0
- package/templates/src/entities/customers/queries.ts +40 -0
- package/templates/src/entities/customers/types.ts +43 -0
- package/templates/src/entities/index.ts +8 -0
- package/templates/src/entities/tasks/api.ts +10 -0
- package/templates/src/entities/tasks/constants.internal.ts +6 -0
- package/templates/src/entities/tasks/constants.ts +39 -0
- package/templates/src/entities/tasks/index.ts +9 -0
- package/templates/src/entities/tasks/mutations.ts +108 -0
- package/templates/src/entities/tasks/queries.ts +42 -0
- package/templates/src/entities/tasks/types.ts +52 -0
- package/templates/src/hooks/useCustomers.ts +17 -0
- package/templates/src/hooks/useTasks.ts +18 -0
- package/templates/src/lib/utils.ts +10 -0
- package/templates/src/styles/globals.css +103 -0
- package/templates/src/types/index.ts +45 -0
- package/templates/tailwind.config.js +108 -0
- package/templates/tsconfig.json +15 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Form Component for Create/Edit
|
|
3
|
+
* Migrated to NativeWind + UI primitives
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect } from "react";
|
|
7
|
+
import { View, ScrollView } from "react-native";
|
|
8
|
+
import { Alert } from "@nextsparkjs/mobile";
|
|
9
|
+
import type {
|
|
10
|
+
Task,
|
|
11
|
+
TaskStatus,
|
|
12
|
+
TaskPriority,
|
|
13
|
+
CreateTaskInput,
|
|
14
|
+
UpdateTaskInput,
|
|
15
|
+
} from "../../../entities/tasks";
|
|
16
|
+
import { STATUS_LABELS, PRIORITY_LABELS } from "../../../entities/tasks";
|
|
17
|
+
import { Text, Input, Textarea, Button, PressableBadge, Card } from "../../ui";
|
|
18
|
+
|
|
19
|
+
type TaskFormProps =
|
|
20
|
+
| {
|
|
21
|
+
mode: "create";
|
|
22
|
+
initialData?: undefined;
|
|
23
|
+
onSubmit: (data: CreateTaskInput) => Promise<void>;
|
|
24
|
+
onDelete?: undefined;
|
|
25
|
+
isLoading?: boolean;
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
mode: "edit";
|
|
29
|
+
initialData: Task;
|
|
30
|
+
onSubmit: (data: UpdateTaskInput) => Promise<void>;
|
|
31
|
+
onDelete: () => Promise<void>;
|
|
32
|
+
isLoading?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const STATUS_OPTIONS: TaskStatus[] = [
|
|
36
|
+
"todo",
|
|
37
|
+
"in-progress",
|
|
38
|
+
"review",
|
|
39
|
+
"done",
|
|
40
|
+
"blocked",
|
|
41
|
+
];
|
|
42
|
+
const PRIORITY_OPTIONS: TaskPriority[] = ["low", "medium", "high", "urgent"];
|
|
43
|
+
|
|
44
|
+
export function TaskForm({
|
|
45
|
+
initialData,
|
|
46
|
+
onSubmit,
|
|
47
|
+
onDelete,
|
|
48
|
+
isLoading = false,
|
|
49
|
+
mode,
|
|
50
|
+
}: TaskFormProps) {
|
|
51
|
+
const [title, setTitle] = useState(initialData?.title || "");
|
|
52
|
+
const [description, setDescription] = useState(
|
|
53
|
+
initialData?.description || ""
|
|
54
|
+
);
|
|
55
|
+
const [status, setStatus] = useState<TaskStatus>(
|
|
56
|
+
initialData?.status || "todo"
|
|
57
|
+
);
|
|
58
|
+
const [priority, setPriority] = useState<TaskPriority>(
|
|
59
|
+
initialData?.priority || "medium"
|
|
60
|
+
);
|
|
61
|
+
const [error, setError] = useState<string | null>(null);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (initialData) {
|
|
65
|
+
setTitle(initialData.title);
|
|
66
|
+
setDescription(initialData.description || "");
|
|
67
|
+
setStatus(initialData.status);
|
|
68
|
+
setPriority(initialData.priority);
|
|
69
|
+
}
|
|
70
|
+
}, [initialData]);
|
|
71
|
+
|
|
72
|
+
const handleSubmit = async () => {
|
|
73
|
+
setError(null);
|
|
74
|
+
|
|
75
|
+
// Validation
|
|
76
|
+
if (!title.trim()) {
|
|
77
|
+
setError("El título es requerido");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const data = {
|
|
82
|
+
title: title.trim(),
|
|
83
|
+
description: description.trim() || undefined,
|
|
84
|
+
status,
|
|
85
|
+
priority,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
await onSubmit(data as any);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
setError(err instanceof Error ? err.message : "Ocurrió un error");
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleDelete = async () => {
|
|
97
|
+
const confirmed = await Alert.confirmDestructive(
|
|
98
|
+
"Eliminar Tarea",
|
|
99
|
+
"¿Estás seguro que deseas eliminar esta tarea? Esta acción no se puede deshacer.",
|
|
100
|
+
"Eliminar"
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (confirmed) {
|
|
104
|
+
try {
|
|
105
|
+
await onDelete?.();
|
|
106
|
+
} catch (err) {
|
|
107
|
+
setError(err instanceof Error ? err.message : "Error al eliminar");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Map status to badge variant
|
|
113
|
+
const getStatusVariant = (s: TaskStatus) => {
|
|
114
|
+
const map: Record<TaskStatus, "todo" | "in-progress" | "review" | "done" | "blocked"> = {
|
|
115
|
+
todo: "todo",
|
|
116
|
+
"in-progress": "in-progress",
|
|
117
|
+
review: "review",
|
|
118
|
+
done: "done",
|
|
119
|
+
blocked: "blocked",
|
|
120
|
+
};
|
|
121
|
+
return map[s];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Map priority to badge variant
|
|
125
|
+
const getPriorityVariant = (p: TaskPriority) => {
|
|
126
|
+
const map: Record<TaskPriority, "low" | "medium" | "high" | "urgent"> = {
|
|
127
|
+
low: "low",
|
|
128
|
+
medium: "medium",
|
|
129
|
+
high: "high",
|
|
130
|
+
urgent: "urgent",
|
|
131
|
+
};
|
|
132
|
+
return map[p];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<ScrollView
|
|
137
|
+
className="flex-1 bg-secondary p-4"
|
|
138
|
+
keyboardShouldPersistTaps="handled"
|
|
139
|
+
>
|
|
140
|
+
{/* Error Display */}
|
|
141
|
+
{error && (
|
|
142
|
+
<Card className="mb-4 border-destructive bg-red-50">
|
|
143
|
+
<Text variant="error">{error}</Text>
|
|
144
|
+
</Card>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{/* Title */}
|
|
148
|
+
<Input
|
|
149
|
+
label="Título"
|
|
150
|
+
required
|
|
151
|
+
value={title}
|
|
152
|
+
onChangeText={setTitle}
|
|
153
|
+
placeholder="Ingresa el título de la tarea..."
|
|
154
|
+
editable={!isLoading}
|
|
155
|
+
containerClassName="mb-5"
|
|
156
|
+
/>
|
|
157
|
+
|
|
158
|
+
{/* Description */}
|
|
159
|
+
<Textarea
|
|
160
|
+
label="Descripción"
|
|
161
|
+
value={description}
|
|
162
|
+
onChangeText={setDescription}
|
|
163
|
+
placeholder="Describe la tarea..."
|
|
164
|
+
editable={!isLoading}
|
|
165
|
+
containerClassName="mb-5"
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
{/* Status */}
|
|
169
|
+
<View className="mb-5 gap-2">
|
|
170
|
+
<Text variant="label">Estado</Text>
|
|
171
|
+
<View className="flex-row flex-wrap gap-2">
|
|
172
|
+
{STATUS_OPTIONS.map((opt) => (
|
|
173
|
+
<PressableBadge
|
|
174
|
+
key={opt}
|
|
175
|
+
variant="outline"
|
|
176
|
+
selectedVariant={getStatusVariant(opt)}
|
|
177
|
+
selected={status === opt}
|
|
178
|
+
onPress={() => setStatus(opt)}
|
|
179
|
+
disabled={isLoading}
|
|
180
|
+
>
|
|
181
|
+
{STATUS_LABELS[opt]}
|
|
182
|
+
</PressableBadge>
|
|
183
|
+
))}
|
|
184
|
+
</View>
|
|
185
|
+
</View>
|
|
186
|
+
|
|
187
|
+
{/* Priority */}
|
|
188
|
+
<View className="mb-5 gap-2">
|
|
189
|
+
<Text variant="label">Prioridad</Text>
|
|
190
|
+
<View className="flex-row flex-wrap gap-2">
|
|
191
|
+
{PRIORITY_OPTIONS.map((opt) => (
|
|
192
|
+
<PressableBadge
|
|
193
|
+
key={opt}
|
|
194
|
+
variant="outline"
|
|
195
|
+
selectedVariant={getPriorityVariant(opt)}
|
|
196
|
+
selected={priority === opt}
|
|
197
|
+
onPress={() => setPriority(opt)}
|
|
198
|
+
disabled={isLoading}
|
|
199
|
+
>
|
|
200
|
+
{PRIORITY_LABELS[opt]}
|
|
201
|
+
</PressableBadge>
|
|
202
|
+
))}
|
|
203
|
+
</View>
|
|
204
|
+
</View>
|
|
205
|
+
|
|
206
|
+
{/* Submit Button */}
|
|
207
|
+
<Button
|
|
208
|
+
onPress={handleSubmit}
|
|
209
|
+
isLoading={isLoading}
|
|
210
|
+
className="mt-2"
|
|
211
|
+
>
|
|
212
|
+
{mode === "create" ? "Crear Tarea" : "Guardar Cambios"}
|
|
213
|
+
</Button>
|
|
214
|
+
|
|
215
|
+
{/* Delete Button (edit mode only) */}
|
|
216
|
+
{mode === "edit" && (
|
|
217
|
+
<Button
|
|
218
|
+
variant="outline-destructive"
|
|
219
|
+
onPress={handleDelete}
|
|
220
|
+
disabled={isLoading}
|
|
221
|
+
className="mt-3"
|
|
222
|
+
>
|
|
223
|
+
Eliminar Tarea
|
|
224
|
+
</Button>
|
|
225
|
+
)}
|
|
226
|
+
|
|
227
|
+
{/* Spacer */}
|
|
228
|
+
<View className="h-10" />
|
|
229
|
+
</ScrollView>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BottomTabBar Component - NextSpark Mobile Style
|
|
3
|
+
* Inicio | Tareas | [Crear] | Clientes | Más
|
|
4
|
+
* Migrated to NativeWind + UI primitives + Lucide icons
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { View, Pressable } from "react-native";
|
|
8
|
+
import { Home, CheckSquare, Plus, Users, Menu, type LucideIcon } from "lucide-react-native";
|
|
9
|
+
import { Text } from "../ui";
|
|
10
|
+
|
|
11
|
+
export type TabKey = "home" | "tasks" | "create" | "customers" | "more";
|
|
12
|
+
|
|
13
|
+
interface BottomTabBarProps {
|
|
14
|
+
activeTab: TabKey;
|
|
15
|
+
onTabPress: (tab: TabKey) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface TabItem {
|
|
19
|
+
key: TabKey;
|
|
20
|
+
label: string;
|
|
21
|
+
Icon: LucideIcon;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const TABS: TabItem[] = [
|
|
25
|
+
{ key: "home", label: "Inicio", Icon: Home },
|
|
26
|
+
{ key: "tasks", label: "Tareas", Icon: CheckSquare },
|
|
27
|
+
{ key: "create", label: "Crear", Icon: Plus },
|
|
28
|
+
{ key: "customers", label: "Clientes", Icon: Users },
|
|
29
|
+
{ key: "more", label: "Más", Icon: Menu },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function BottomTabBar({ activeTab, onTabPress }: BottomTabBarProps) {
|
|
33
|
+
return (
|
|
34
|
+
<View className="flex-row items-end justify-around border-t border-border bg-background pb-2 pt-2">
|
|
35
|
+
{TABS.map((tab) => {
|
|
36
|
+
const isActive = activeTab === tab.key;
|
|
37
|
+
const isCreate = tab.key === "create";
|
|
38
|
+
const IconComponent = tab.Icon;
|
|
39
|
+
|
|
40
|
+
if (isCreate) {
|
|
41
|
+
return (
|
|
42
|
+
<Pressable
|
|
43
|
+
key={tab.key}
|
|
44
|
+
className="-mt-6 flex-1 items-center"
|
|
45
|
+
onPress={() => onTabPress(tab.key)}
|
|
46
|
+
>
|
|
47
|
+
<View className="h-14 w-14 items-center justify-center rounded-full bg-primary shadow-lg">
|
|
48
|
+
<IconComponent size={28} color="#fafafa" strokeWidth={1.5} />
|
|
49
|
+
</View>
|
|
50
|
+
<Text className="mt-1 text-[11px] font-medium text-muted-foreground">
|
|
51
|
+
{tab.label}
|
|
52
|
+
</Text>
|
|
53
|
+
</Pressable>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Pressable
|
|
59
|
+
key={tab.key}
|
|
60
|
+
className="flex-1 items-center justify-center py-2"
|
|
61
|
+
onPress={() => onTabPress(tab.key)}
|
|
62
|
+
>
|
|
63
|
+
<IconComponent
|
|
64
|
+
size={24}
|
|
65
|
+
color={isActive ? "#171717" : "#737373"}
|
|
66
|
+
strokeWidth={isActive ? 2 : 1.5}
|
|
67
|
+
/>
|
|
68
|
+
<Text
|
|
69
|
+
className={`mt-1 text-[11px] font-medium ${
|
|
70
|
+
isActive ? "text-foreground" : "text-muted-foreground"
|
|
71
|
+
}`}
|
|
72
|
+
>
|
|
73
|
+
{tab.label}
|
|
74
|
+
</Text>
|
|
75
|
+
</Pressable>
|
|
76
|
+
);
|
|
77
|
+
})}
|
|
78
|
+
</View>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreateSheet Component - "Crear" bottom sheet for quick entity creation
|
|
3
|
+
* Migrated to NativeWind + Lucide icons
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { View, TouchableOpacity, Modal, Pressable } from "react-native";
|
|
7
|
+
import { Users, CheckSquare, X, type LucideIcon } from "lucide-react-native";
|
|
8
|
+
import { Text } from "../ui";
|
|
9
|
+
|
|
10
|
+
interface CreateSheetProps {
|
|
11
|
+
visible: boolean;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
onCreateEntity: (entity: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface CreateOption {
|
|
17
|
+
key: string;
|
|
18
|
+
label: string;
|
|
19
|
+
description: string;
|
|
20
|
+
Icon: LucideIcon;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CREATE_OPTIONS: CreateOption[] = [
|
|
24
|
+
{
|
|
25
|
+
key: "customer",
|
|
26
|
+
label: "customer",
|
|
27
|
+
description: "Crear nuevo customer",
|
|
28
|
+
Icon: Users,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: "task",
|
|
32
|
+
label: "task",
|
|
33
|
+
description: "Crear nuevo task",
|
|
34
|
+
Icon: CheckSquare,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export function CreateSheet({
|
|
39
|
+
visible,
|
|
40
|
+
onClose,
|
|
41
|
+
onCreateEntity,
|
|
42
|
+
}: CreateSheetProps) {
|
|
43
|
+
const handleCreate = (option: CreateOption) => {
|
|
44
|
+
onCreateEntity(option.key);
|
|
45
|
+
onClose();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Modal
|
|
50
|
+
visible={visible}
|
|
51
|
+
transparent
|
|
52
|
+
animationType="slide"
|
|
53
|
+
onRequestClose={onClose}
|
|
54
|
+
>
|
|
55
|
+
<Pressable
|
|
56
|
+
className="flex-1 justify-end bg-black/50"
|
|
57
|
+
onPress={onClose}
|
|
58
|
+
>
|
|
59
|
+
<Pressable
|
|
60
|
+
className="rounded-t-[20px] bg-background px-6 pb-10 pt-3"
|
|
61
|
+
onPress={(e) => e.stopPropagation()}
|
|
62
|
+
>
|
|
63
|
+
{/* Handle bar */}
|
|
64
|
+
<View className="mb-4 h-1 w-10 self-center rounded-full bg-border" />
|
|
65
|
+
|
|
66
|
+
{/* Close button */}
|
|
67
|
+
<TouchableOpacity
|
|
68
|
+
className="absolute right-5 top-5 h-8 w-8 items-center justify-center rounded-full border border-border"
|
|
69
|
+
onPress={onClose}
|
|
70
|
+
>
|
|
71
|
+
<X size={16} color="#737373" />
|
|
72
|
+
</TouchableOpacity>
|
|
73
|
+
|
|
74
|
+
{/* Header */}
|
|
75
|
+
<Text className="mt-2 text-center text-xl font-semibold">Crear</Text>
|
|
76
|
+
<Text className="mb-6 mt-2 text-center text-sm text-muted-foreground">
|
|
77
|
+
Crea un nuevo elemento
|
|
78
|
+
</Text>
|
|
79
|
+
|
|
80
|
+
{/* Options */}
|
|
81
|
+
<View className="gap-2">
|
|
82
|
+
{CREATE_OPTIONS.map((option) => {
|
|
83
|
+
const IconComponent = option.Icon;
|
|
84
|
+
return (
|
|
85
|
+
<TouchableOpacity
|
|
86
|
+
key={option.key}
|
|
87
|
+
className="flex-row items-center gap-4 py-4"
|
|
88
|
+
onPress={() => handleCreate(option)}
|
|
89
|
+
activeOpacity={0.7}
|
|
90
|
+
>
|
|
91
|
+
<View className="w-8 items-center">
|
|
92
|
+
<IconComponent size={24} color="#171717" />
|
|
93
|
+
</View>
|
|
94
|
+
<View className="flex-1">
|
|
95
|
+
<Text className="text-base font-semibold">{option.label}</Text>
|
|
96
|
+
<Text className="mt-0.5 text-sm text-muted-foreground">
|
|
97
|
+
{option.description}
|
|
98
|
+
</Text>
|
|
99
|
+
</View>
|
|
100
|
+
</TouchableOpacity>
|
|
101
|
+
);
|
|
102
|
+
})}
|
|
103
|
+
</View>
|
|
104
|
+
</Pressable>
|
|
105
|
+
</Pressable>
|
|
106
|
+
</Modal>
|
|
107
|
+
);
|
|
108
|
+
}
|