@acvl/mobile-components 0.0.2 → 0.0.4

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 (42) hide show
  1. package/README.md +2 -2
  2. package/lib/module/camara/CampoDeFotos.js +34 -12
  3. package/lib/module/camara/CampoDeFotos.js.map +1 -1
  4. package/lib/module/camara/index.js.map +1 -1
  5. package/lib/module/componentes/ContenidoDePantalla.js +2 -1
  6. package/lib/module/componentes/ContenidoDePantalla.js.map +1 -1
  7. package/lib/module/formularios/SelectorDeOpcion.js +19 -27
  8. package/lib/module/formularios/SelectorDeOpcion.js.map +1 -1
  9. package/lib/module/formularios/TextoDeAccesoControlado.js +1 -1
  10. package/lib/module/formularios/campos.js +257 -129
  11. package/lib/module/formularios/campos.js.map +1 -1
  12. package/lib/module/hoja/HojaDeLista.js +8 -3
  13. package/lib/module/hoja/HojaDeLista.js.map +1 -1
  14. package/lib/module/hoja/HojaInferior.js +8 -3
  15. package/lib/module/hoja/HojaInferior.js.map +1 -1
  16. package/lib/module/index.js +1 -1
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/typescript/src/camara/CampoDeFotos.d.ts +17 -4
  19. package/lib/typescript/src/camara/CampoDeFotos.d.ts.map +1 -1
  20. package/lib/typescript/src/camara/index.d.ts +1 -1
  21. package/lib/typescript/src/camara/index.d.ts.map +1 -1
  22. package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts +6 -1
  23. package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts.map +1 -1
  24. package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts +10 -0
  25. package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts.map +1 -1
  26. package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts +1 -1
  27. package/lib/typescript/src/formularios/campos.d.ts +43 -8
  28. package/lib/typescript/src/formularios/campos.d.ts.map +1 -1
  29. package/lib/typescript/src/hoja/HojaDeLista.d.ts.map +1 -1
  30. package/lib/typescript/src/hoja/HojaInferior.d.ts.map +1 -1
  31. package/lib/typescript/src/index.d.ts +1 -1
  32. package/lib/typescript/src/index.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/camara/CampoDeFotos.tsx +41 -12
  35. package/src/camara/index.ts +6 -1
  36. package/src/componentes/ContenidoDePantalla.tsx +12 -2
  37. package/src/formularios/SelectorDeOpcion.tsx +40 -22
  38. package/src/formularios/TextoDeAccesoControlado.tsx +1 -1
  39. package/src/formularios/campos.tsx +276 -123
  40. package/src/hoja/HojaDeLista.tsx +15 -9
  41. package/src/hoja/HojaInferior.tsx +15 -9
  42. package/src/index.ts +1 -0
@@ -1,15 +1,21 @@
1
1
  import DateTimePicker from "@react-native-community/datetimepicker";
2
2
  import dayjs from "dayjs";
3
3
  import React from "react";
4
- import { Pressable, Switch, View, type KeyboardTypeOptions } from "react-native";
4
+ import {
5
+ Pressable,
6
+ Switch,
7
+ View,
8
+ type AccessibilityRole,
9
+ type KeyboardTypeOptions,
10
+ } from "react-native";
5
11
  import IconoDeRenglon from "../componentes/IconoDeRenglon";
6
12
  import OpcionesExcluyentes from "../componentes/OpcionesExcluyentes";
7
13
  import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
8
14
  import { Text, TextInput } from "../texto";
9
15
 
10
16
  /**
11
- * Los campos de un formulario (un viaje, un tracto, un operador). Cada uno trae su rótulo arriba y
12
- * su error abajo, para que la pantalla sólo los apile. Son controlados: atados a react-hook-form,
17
+ * Los campos de un formulario (un viaje, un tracto, un operador). Cada uno es una caja con su
18
+ * rótulo adentro (`CajaDeCampo`) y su error abajo, para que la pantalla sólo los apile. Son controlados: atados a react-hook-form,
13
19
  * van por `controlados.tsx`.
14
20
  */
15
21
 
@@ -20,27 +26,40 @@ export function ErrorDelCampo({ error }: { error?: string }) {
20
26
  }
21
27
 
22
28
  /**
23
- * El marco de un campo: el rótulo arriba, lo que se captura en medio, y debajo la ayuda y el error.
29
+ * El marco de un campo: el rótulo arriba (si no va en su `CajaDeCampo`), lo que se captura en
30
+ * medio, y debajo la ayuda y el error.
24
31
  * Lo usan todos los campos de aquí, y una app lo usa para lo que no es un campo de la casa —una
25
32
  * tira de fotos, el renglón de un escáner— sin que se vea distinto de los demás.
26
33
  *
27
34
  * Sin `etiqueta` no pinta rótulo: el interruptor lleva el suyo adentro.
35
+ *
36
+ * `accesorio` va a la derecha del rótulo: un «No aplica» que apaga el campo, por ejemplo. Queda
37
+ * fuera de lo que se apaga, porque es justo lo que lo vuelve a prender.
28
38
  */
29
39
  export function Campo({
30
40
  etiqueta,
31
41
  ayuda,
32
42
  error,
43
+ accesorio,
33
44
  children,
34
45
  }: {
35
46
  etiqueta?: string;
36
47
  ayuda?: string;
37
48
  error?: string;
49
+ accesorio?: React.ReactNode;
38
50
  children: React.ReactNode;
39
51
  }) {
40
52
  const styles = useStyles();
41
53
  return (
42
54
  <View style={styles.campo}>
43
- {etiqueta ? <Text style={styles.etiqueta}>{etiqueta}</Text> : null}
55
+ {accesorio ? (
56
+ <View style={styles.rotuloConAccesorio}>
57
+ <Text style={[styles.etiqueta, styles.flex]}>{etiqueta ?? ""}</Text>
58
+ {accesorio}
59
+ </View>
60
+ ) : etiqueta ? (
61
+ <Text style={styles.etiqueta}>{etiqueta}</Text>
62
+ ) : null}
44
63
  {children}
45
64
  {ayuda ? <Text style={styles.ayuda}>{ayuda}</Text> : null}
46
65
  <ErrorDelCampo error={error} />
@@ -48,6 +67,79 @@ export function Campo({
48
67
  );
49
68
  }
50
69
 
70
+ /**
71
+ * La caja de un campo: el rótulo adentro, arriba de lo que se captura, con un borde tenue. Es la
72
+ * forma del texto, la fecha, el teléfono y el selector, y una app la usa para los suyos (el
73
+ * renglón de un escáner) sin que se vean distintos. Va dentro de un `Campo` sin `etiqueta`, que
74
+ * pone la ayuda y el error debajo.
75
+ *
76
+ * Tocarla en cualquier parte es `onPress`: enfocar lo que se escribe, abrir la lista. Apagada se
77
+ * atenúa lo de adentro y no el `accesorio`, que es justo lo que la vuelve a prender.
78
+ */
79
+ export function CajaDeCampo({
80
+ etiqueta,
81
+ accesorio,
82
+ onPress,
83
+ accessibilityRole,
84
+ disabled,
85
+ enfocada,
86
+ pendiente,
87
+ conError,
88
+ children,
89
+ }: {
90
+ etiqueta?: string;
91
+ accesorio?: React.ReactNode;
92
+ onPress?: () => void;
93
+ accessibilityRole?: AccessibilityRole;
94
+ disabled?: boolean;
95
+ /** Se escribe en ella: el borde se marca. */
96
+ enfocada?: boolean;
97
+ /** Vacía y se pidió resaltarla (`resaltarVacio`): el borde en ámbar. */
98
+ pendiente?: boolean;
99
+ /** El borde en rojo; el texto del error lo pone el `Campo`. */
100
+ conError?: boolean;
101
+ children: React.ReactNode;
102
+ }) {
103
+ const styles = useStyles();
104
+ return (
105
+ <Pressable
106
+ onPress={onPress}
107
+ disabled={disabled || !onPress}
108
+ accessibilityRole={accessibilityRole}
109
+ accessibilityState={{ disabled: !!disabled }}
110
+ style={[
111
+ styles.caja,
112
+ enfocada && styles.cajaEnfocada,
113
+ pendiente && !disabled && styles.cajaPendiente,
114
+ conError && styles.cajaError,
115
+ ]}
116
+ >
117
+ {etiqueta || accesorio ? (
118
+ <View style={styles.rotuloConAccesorio}>
119
+ <Text style={[styles.etiqueta, styles.flex, disabled && styles.inactivo]}>
120
+ {etiqueta ?? ""}
121
+ </Text>
122
+ {accesorio}
123
+ </View>
124
+ ) : null}
125
+ <View style={disabled ? styles.inactivo : undefined}>{children}</View>
126
+ </Pressable>
127
+ );
128
+ }
129
+
130
+ /** Lo que se escribe dentro de una caja: el enfoque (para el borde) y tocar la caja para escribir. */
131
+ function useEntradaEnCaja() {
132
+ const entrada = React.useRef<React.ComponentRef<typeof TextInput>>(null);
133
+ const [enfocada, setEnfocada] = React.useState(false);
134
+ return {
135
+ entrada,
136
+ enfocada,
137
+ enfocar: () => entrada.current?.focus(),
138
+ alEnfocar: () => setEnfocada(true),
139
+ alSalir: () => setEnfocada(false),
140
+ };
141
+ }
142
+
51
143
  /**
52
144
  * Un texto libre: una referencia (tope de 100), unas instrucciones (varios renglones), un VIN (en
53
145
  * mayúsculas) o un año (`teclado="number-pad"`).
@@ -64,6 +156,8 @@ export function CampoDeTexto({
64
156
  disabled,
65
157
  teclado,
66
158
  mayusculas,
159
+ accesorio,
160
+ resaltarVacio,
67
161
  }: {
68
162
  etiqueta: string;
69
163
  valor: string;
@@ -77,27 +171,45 @@ export function CampoDeTexto({
77
171
  teclado?: KeyboardTypeOptions;
78
172
  /** Cómo pone mayúsculas el teclado; un VIN o una placa, `characters`. */
79
173
  mayusculas?: "none" | "sentences" | "words" | "characters";
174
+ /** Lo que va a la derecha del rótulo (ver `Campo`). */
175
+ accesorio?: React.ReactNode;
176
+ /**
177
+ * Señala el campo mientras esté vacío: lo que falta capturar en un formulario largo (una
178
+ * revisión) se ve sin buscarlo. En ámbar y no en rojo, que es del error.
179
+ */
180
+ resaltarVacio?: boolean;
80
181
  }) {
81
182
  const styles = useStyles();
183
+ const { entrada, enfocada, enfocar, alEnfocar, alSalir } = useEntradaEnCaja();
184
+ const pendiente = resaltarVacio && !error && !String(valor ?? "").trim();
82
185
  return (
83
- <Campo etiqueta={etiqueta} ayuda={ayuda} error={error}>
84
- <TextInput
85
- value={valor}
86
- onChangeText={onCambiar}
87
- keyboardType={teclado}
88
- autoCapitalize={mayusculas}
89
- autoCorrect={mayusculas === "characters" ? false : undefined}
90
- multiline={multilinea}
91
- maxLength={maximo}
92
- placeholder={placeholder}
93
- editable={!disabled}
94
- style={[
95
- styles.entrada,
96
- multilinea && styles.multilinea,
97
- !!error && styles.entradaError,
98
- disabled && styles.inactivo,
99
- ]}
100
- />
186
+ <Campo ayuda={ayuda} error={error}>
187
+ <CajaDeCampo
188
+ etiqueta={etiqueta}
189
+ accesorio={accesorio}
190
+ onPress={enfocar}
191
+ disabled={disabled}
192
+ enfocada={enfocada}
193
+ pendiente={pendiente}
194
+ conError={!!error}
195
+ >
196
+ <TextInput
197
+ ref={entrada}
198
+ value={valor}
199
+ onChangeText={onCambiar}
200
+ onFocus={alEnfocar}
201
+ onBlur={alSalir}
202
+ keyboardType={teclado}
203
+ autoCapitalize={mayusculas}
204
+ autoCorrect={mayusculas === "characters" ? false : undefined}
205
+ multiline={multilinea}
206
+ maxLength={maximo}
207
+ placeholder={placeholder}
208
+ editable={!disabled}
209
+ accessibilityLabel={etiqueta}
210
+ style={[styles.texto, multilinea && styles.multilinea]}
211
+ />
212
+ </CajaDeCampo>
101
213
  </Campo>
102
214
  );
103
215
  }
@@ -107,8 +219,8 @@ export function CampoDeTexto({
107
219
  * hora es el dato** (contra ella se mide si un viaje va tarde o un equipo está comprometido), así
108
220
  * que nunca se manda sólo el día.
109
221
  *
110
- * Vacío, un solo botón «Elegir»: abre el día y, al elegirlo, la hora. Si es `opcional`, «Quitar»
111
- * lo deja vacío otra vez.
222
+ * Vacío, un solo botón «Elegir»: abre el día y, al elegirlo, la hora. Si es `opcional`, la ✕ del
223
+ * rótulo lo deja vacío otra vez.
112
224
  */
113
225
  export function CampoDeFechaHora({
114
226
  etiqueta,
@@ -136,31 +248,48 @@ export function CampoDeFechaHora({
136
248
  // Vacío, el selector arranca en la próxima hora en punto: la hora de ahora no le sirve a nadie.
137
249
  const base = valor ?? dayjs().add(1, "hour").startOf("hour").toDate();
138
250
 
251
+ // Puesta, el día y la hora se tocan cada uno; el resto de la caja abre el día.
139
252
  return (
140
- <Campo etiqueta={etiqueta} ayuda={ayuda} error={error}>
141
- {valor ? (
142
- <View style={styles.fechaHora}>
143
- <Pressable
144
- onPress={() => setEligiendo("date")}
145
- accessibilityRole="button"
146
- style={[styles.entrada, styles.mitad, !!error && styles.entradaError]}
147
- >
148
- <Text style={styles.textoFecha}>{dayjs(valor).format("D MMM YYYY")}</Text>
149
- </Pressable>
150
- <Pressable
151
- onPress={() => setEligiendo("time")}
152
- accessibilityRole="button"
153
- style={[styles.entrada, styles.mitad, !!error && styles.entradaError]}
154
- >
155
- <Text style={styles.textoFecha}>{dayjs(valor).format("HH:mm")}</Text>
156
- </Pressable>
157
- </View>
158
- ) : (
159
- <Pressable
160
- onPress={() => setEligiendo("date")}
161
- accessibilityRole="button"
162
- style={[styles.entrada, !!error && styles.entradaError]}
163
- >
253
+ <Campo ayuda={ayuda} error={error}>
254
+ <CajaDeCampo
255
+ etiqueta={etiqueta}
256
+ onPress={() => setEligiendo("date")}
257
+ accessibilityRole="button"
258
+ conError={!!error}
259
+ accesorio={
260
+ opcional && valor ? <BotonDeQuitar onPress={() => onCambiar(null)} /> : null
261
+ }
262
+ >
263
+ {valor ? (
264
+ <View style={styles.fechaHora}>
265
+ <Pressable
266
+ onPress={() => setEligiendo("date")}
267
+ accessibilityRole="button"
268
+ style={styles.renglon}
269
+ >
270
+ <IconoDeRenglon
271
+ name="calendar"
272
+ fontSize={tipografia.subtitulo}
273
+ color={c.secondary}
274
+ />
275
+ <Text style={styles.textoFecha}>
276
+ {dayjs(valor).format("D MMM YYYY")}
277
+ </Text>
278
+ </Pressable>
279
+ <Pressable
280
+ onPress={() => setEligiendo("time")}
281
+ accessibilityRole="button"
282
+ style={styles.renglon}
283
+ >
284
+ <IconoDeRenglon
285
+ name="clock-outline"
286
+ fontSize={tipografia.subtitulo}
287
+ color={c.secondary}
288
+ />
289
+ <Text style={styles.textoFecha}>{dayjs(valor).format("HH:mm")}</Text>
290
+ </Pressable>
291
+ </View>
292
+ ) : (
164
293
  <View style={styles.renglon}>
165
294
  <IconoDeRenglon
166
295
  name="calendar-clock"
@@ -169,22 +298,8 @@ export function CampoDeFechaHora({
169
298
  />
170
299
  <Text style={[styles.textoFecha, styles.sinValor]}>Elegir…</Text>
171
300
  </View>
172
- </Pressable>
173
- )}
174
- {opcional && valor ? (
175
- <Pressable
176
- onPress={() => onCambiar(null)}
177
- accessibilityRole="button"
178
- style={styles.quitar}
179
- >
180
- <IconoDeRenglon
181
- name="close-circle-outline"
182
- fontSize={tipografia.cuerpo}
183
- color={c.secondary}
184
- />
185
- <Text style={styles.quitarTexto}>Quitar</Text>
186
- </Pressable>
187
- ) : null}
301
+ )}
302
+ </CajaDeCampo>
188
303
  {eligiendo ? (
189
304
  <DateTimePicker
190
305
  value={base}
@@ -217,6 +332,25 @@ export function CampoDeFechaHora({
217
332
  );
218
333
  }
219
334
 
335
+ /** El «Quitar» de una fecha opcional: la ✕ junto al rótulo, dentro de la caja. */
336
+ function BotonDeQuitar({ onPress }: { onPress: () => void }) {
337
+ const c = usePaleta();
338
+ return (
339
+ <Pressable
340
+ onPress={onPress}
341
+ hitSlop={10}
342
+ accessibilityRole="button"
343
+ accessibilityLabel="Quitar"
344
+ >
345
+ <IconoDeRenglon
346
+ name="close-circle"
347
+ fontSize={tipografia.subtitulo}
348
+ color={c.secondaryLight}
349
+ />
350
+ </Pressable>
351
+ );
352
+ }
353
+
220
354
  /** Un interruptor con su texto y, si hace falta, por qué está como está. */
221
355
  export function CampoInterruptor({
222
356
  texto,
@@ -287,11 +421,15 @@ export function CampoDeFecha({
287
421
  const fecha = valor ? dayjs(valor) : null;
288
422
 
289
423
  return (
290
- <Campo etiqueta={etiqueta} ayuda={ayuda} error={error}>
291
- <Pressable
424
+ <Campo ayuda={ayuda} error={error}>
425
+ <CajaDeCampo
426
+ etiqueta={etiqueta}
292
427
  onPress={() => setEligiendo(true)}
293
428
  accessibilityRole="button"
294
- style={[styles.entrada, !!error && styles.entradaError]}
429
+ conError={!!error}
430
+ accesorio={
431
+ opcional && valor ? <BotonDeQuitar onPress={() => onCambiar(null)} /> : null
432
+ }
295
433
  >
296
434
  <View style={styles.renglon}>
297
435
  <IconoDeRenglon
@@ -303,21 +441,7 @@ export function CampoDeFecha({
303
441
  {fecha ? fecha.format("D MMM YYYY") : "Elegir…"}
304
442
  </Text>
305
443
  </View>
306
- </Pressable>
307
- {opcional && valor ? (
308
- <Pressable
309
- onPress={() => onCambiar(null)}
310
- accessibilityRole="button"
311
- style={styles.quitar}
312
- >
313
- <IconoDeRenglon
314
- name="close-circle-outline"
315
- fontSize={tipografia.cuerpo}
316
- color={c.secondary}
317
- />
318
- <Text style={styles.quitarTexto}>Quitar</Text>
319
- </Pressable>
320
- ) : null}
444
+ </CajaDeCampo>
321
445
  {eligiendo ? (
322
446
  <DateTimePicker
323
447
  value={fecha?.toDate() ?? new Date()}
@@ -402,42 +526,63 @@ export function CampoDeTelefono({
402
526
  }) {
403
527
  const c = usePaleta();
404
528
  const styles = useStyles();
529
+ const { entrada, enfocada, enfocar, alEnfocar, alSalir } = useEntradaEnCaja();
405
530
  const { lada, digitos } = partirTelefono(valor);
406
531
  const armar = (nuevaLada: string, nuevosDigitos: string) => {
407
532
  const solo = nuevosDigitos.replace(/\D/g, "");
408
533
  onCambiar(solo ? `${nuevaLada}${solo}` : null);
409
534
  };
410
535
  return (
411
- <Campo etiqueta={etiqueta} ayuda={ayuda} error={error}>
412
- <View style={styles.telefono}>
413
- {LADAS.map((l) => (
414
- <Pressable
415
- key={l.pais}
416
- onPress={() => armar(l.lada, digitos)}
417
- accessibilityRole="radio"
418
- accessibilityState={{ selected: lada === l.lada }}
419
- style={[styles.lada, lada === l.lada && styles.ladaElegida]}
420
- >
421
- <Text style={[styles.ladaTexto, lada === l.lada && { color: c.primary }]}>
422
- {l.texto}
423
- </Text>
424
- </Pressable>
425
- ))}
426
- </View>
427
- <TextInput
428
- value={digitos}
429
- onChangeText={(texto) => armar(lada, texto)}
430
- keyboardType="phone-pad"
431
- maxLength={10}
432
- placeholder="10 dígitos"
433
- style={[styles.entrada, !!error && styles.entradaError]}
434
- />
536
+ <Campo ayuda={ayuda} error={error}>
537
+ <CajaDeCampo
538
+ etiqueta={etiqueta}
539
+ onPress={enfocar}
540
+ enfocada={enfocada}
541
+ conError={!!error}
542
+ >
543
+ <View style={styles.telefono}>
544
+ <View style={styles.ladas}>
545
+ {LADAS.map((l) => (
546
+ <Pressable
547
+ key={l.pais}
548
+ onPress={() => armar(l.lada, digitos)}
549
+ accessibilityRole="radio"
550
+ accessibilityState={{ selected: lada === l.lada }}
551
+ style={[styles.lada, lada === l.lada && styles.ladaElegida]}
552
+ >
553
+ <Text
554
+ style={[
555
+ styles.ladaTexto,
556
+ lada === l.lada && { color: c.primary },
557
+ ]}
558
+ >
559
+ {l.texto}
560
+ </Text>
561
+ </Pressable>
562
+ ))}
563
+ </View>
564
+ <TextInput
565
+ ref={entrada}
566
+ value={digitos}
567
+ onChangeText={(texto) => armar(lada, texto)}
568
+ onFocus={alEnfocar}
569
+ onBlur={alSalir}
570
+ keyboardType="phone-pad"
571
+ maxLength={10}
572
+ placeholder="10 dígitos"
573
+ accessibilityLabel={etiqueta}
574
+ style={styles.texto}
575
+ />
576
+ </View>
577
+ </CajaDeCampo>
435
578
  </Campo>
436
579
  );
437
580
  }
438
581
 
439
582
  const useStyles = crearEstilos((c) => ({
440
583
  campo: { gap: 6 },
584
+ flex: { flex: 1 },
585
+ rotuloConAccesorio: { flexDirection: "row", alignItems: "center", gap: 8 },
441
586
  etiqueta: {
442
587
  ...renglon(tipografia.etiqueta),
443
588
  fontWeight: "800",
@@ -445,36 +590,42 @@ const useStyles = crearEstilos((c) => ({
445
590
  textTransform: "uppercase",
446
591
  letterSpacing: 0.8,
447
592
  },
448
- entrada: {
449
- borderWidth: 1.5,
450
- borderColor: c.secondaryLight,
451
- borderRadius: 12,
452
- paddingHorizontal: 14,
453
- paddingVertical: 12,
593
+ // La caja con el borde tenue del selector; marcado al escribir, ámbar si falta, rojo con error.
594
+ caja: {
454
595
  backgroundColor: c.superficie,
455
- fontSize: tipografia.subtitulo,
596
+ borderRadius: 14,
597
+ borderWidth: 2,
598
+ borderColor: c.secondaryLighter,
599
+ padding: 14,
600
+ gap: 4,
601
+ },
602
+ cajaEnfocada: { borderColor: c.secondary },
603
+ cajaPendiente: { borderColor: c.aviso.fuerte },
604
+ cajaError: { borderColor: c.primary },
605
+ // Sin el relleno que Android le pone a un TextInput: el de la caja ya basta.
606
+ texto: {
607
+ ...renglon(tipografia.subtitulo),
608
+ fontWeight: "700",
456
609
  color: c.texto,
610
+ padding: 0,
457
611
  },
458
- multilinea: { minHeight: 100, textAlignVertical: "top", fontSize: tipografia.cuerpo },
459
- entradaError: { borderColor: c.primary },
612
+ multilinea: { ...renglon(tipografia.cuerpo), minHeight: 80, textAlignVertical: "top" },
460
613
  inactivo: { opacity: 0.5 },
461
- fechaHora: { flexDirection: "row", gap: 12 },
462
- mitad: { flex: 1, justifyContent: "center" },
614
+ fechaHora: { flexDirection: "row", flexWrap: "wrap", gap: 20 },
463
615
  renglon: { flexDirection: "row", alignItems: "flex-start", gap: 8 },
464
- textoFecha: { ...renglon(tipografia.cuerpo), fontWeight: "700", color: c.texto },
616
+ textoFecha: { ...renglon(tipografia.subtitulo), fontWeight: "800", color: c.texto },
465
617
  sinValor: { color: c.secondaryLight, fontWeight: "600" },
466
- quitar: { flexDirection: "row", alignItems: "flex-start", gap: 6, alignSelf: "flex-start" },
467
- quitarTexto: { ...renglon(tipografia.cuerpo), fontWeight: "700", color: c.secondary },
468
618
  ayuda: { ...renglon(tipografia.secundario), color: c.secondary },
469
619
  error: { ...renglon(tipografia.secundario), color: c.primary },
470
- telefono: { flexDirection: "row", gap: 10 },
620
+ telefono: { gap: 10 },
621
+ ladas: { flexDirection: "row", gap: 10 },
471
622
  lada: {
472
623
  flex: 1,
473
624
  alignItems: "center",
474
625
  borderWidth: 2,
475
626
  borderColor: c.secondaryLighter,
476
627
  borderRadius: 12,
477
- paddingVertical: 10,
628
+ paddingVertical: 8,
478
629
  backgroundColor: c.superficie,
479
630
  },
480
631
  ladaElegida: { borderColor: c.primary, backgroundColor: c.primaryWash },
@@ -485,6 +636,8 @@ const useStyles = crearEstilos((c) => ({
485
636
  gap: 10,
486
637
  backgroundColor: c.superficie,
487
638
  borderRadius: 14,
639
+ borderWidth: 2,
640
+ borderColor: c.secondaryLighter,
488
641
  paddingHorizontal: 14,
489
642
  paddingVertical: 10,
490
643
  },
@@ -4,7 +4,8 @@ import BottomSheet, {
4
4
  BottomSheetTextInput,
5
5
  type BottomSheetBackdropProps,
6
6
  } from "@gorhom/bottom-sheet";
7
- import React, { useEffect, useRef, useState } from "react";
7
+ import { useFocusEffect } from "expo-router";
8
+ import React, { useCallback, useEffect, useRef, useState } from "react";
8
9
  import { ActivityIndicator, BackHandler, View } from "react-native";
9
10
  import { useSafeAreaInsets } from "react-native-safe-area-context";
10
11
  import BotonGrande from "../componentes/BotonGrande";
@@ -89,14 +90,19 @@ export default function HojaDeLista<T>({
89
90
  else hoja.current?.close();
90
91
  }, [abierta]);
91
92
 
92
- useEffect(() => {
93
- if (!abierta) return;
94
- const sub = BackHandler.addEventListener("hardwareBackPress", () => {
95
- onCerrar();
96
- return true;
97
- });
98
- return () => sub.remove();
99
- }, [abierta, onCerrar]);
93
+ // Atrás la cierra **sólo con su pantalla al frente**: si desde la hoja se abre una ficha, la
94
+ // hoja sigue abierta debajo, y sin esto el primer atrás de la ficha la cerraba a ella en vez de
95
+ // regresar. La flecha del encabezado no pasa por aquí (`router.back()`), por eso ésa sí servía.
96
+ useFocusEffect(
97
+ useCallback(() => {
98
+ if (!abierta) return;
99
+ const sub = BackHandler.addEventListener("hardwareBackPress", () => {
100
+ onCerrar();
101
+ return true;
102
+ });
103
+ return () => sub.remove();
104
+ }, [abierta, onCerrar]),
105
+ );
100
106
 
101
107
  const { renglones, total } = lista;
102
108
 
@@ -6,7 +6,8 @@ import BottomSheet, {
6
6
  BottomSheetScrollView,
7
7
  type BottomSheetBackdropProps,
8
8
  } from "@gorhom/bottom-sheet";
9
- import React, { useEffect, useRef } from "react";
9
+ import { useFocusEffect } from "expo-router";
10
+ import React, { useCallback, useEffect, useRef } from "react";
10
11
  import { BackHandler, useWindowDimensions, View } from "react-native";
11
12
  import { useSafeAreaInsets } from "react-native-safe-area-context";
12
13
 
@@ -48,14 +49,19 @@ export default function HojaInferior({
48
49
  else hoja.current?.close();
49
50
  }, [abierta]);
50
51
 
51
- useEffect(() => {
52
- if (!abierta) return;
53
- const sub = BackHandler.addEventListener("hardwareBackPress", () => {
54
- onCerrar();
55
- return true;
56
- });
57
- return () => sub.remove();
58
- }, [abierta, onCerrar]);
52
+ // Atrás la cierra **sólo con su pantalla al frente**: si desde la hoja se abre una ficha, la
53
+ // hoja sigue abierta debajo, y sin esto el primer atrás de la ficha la cerraba a ella en vez de
54
+ // regresar. La flecha del encabezado no pasa por aquí (`router.back()`), por eso ésa sí servía.
55
+ useFocusEffect(
56
+ useCallback(() => {
57
+ if (!abierta) return;
58
+ const sub = BackHandler.addEventListener("hardwareBackPress", () => {
59
+ onCerrar();
60
+ return true;
61
+ });
62
+ return () => sub.remove();
63
+ }, [abierta, onCerrar]),
64
+ );
59
65
 
60
66
  return (
61
67
  <BottomSheet
package/src/index.ts CHANGED
@@ -62,6 +62,7 @@ export {
62
62
  // ==============================|| FORMULARIOS ||============================== //
63
63
 
64
64
  export {
65
+ CajaDeCampo,
65
66
  CampoDeFecha,
66
67
  CampoDeFechaHora,
67
68
  CampoDeOpciones,