@acvl/mobile-components 0.0.3 → 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.
- package/README.md +2 -2
- package/lib/module/camara/CampoDeFotos.js +34 -12
- package/lib/module/camara/CampoDeFotos.js.map +1 -1
- package/lib/module/camara/index.js.map +1 -1
- package/lib/module/formularios/SelectorDeOpcion.js +19 -27
- package/lib/module/formularios/SelectorDeOpcion.js.map +1 -1
- package/lib/module/formularios/TextoDeAccesoControlado.js +1 -1
- package/lib/module/formularios/campos.js +257 -129
- package/lib/module/formularios/campos.js.map +1 -1
- package/lib/module/hoja/HojaDeLista.js +8 -3
- package/lib/module/hoja/HojaDeLista.js.map +1 -1
- package/lib/module/hoja/HojaInferior.js +8 -3
- package/lib/module/hoja/HojaInferior.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/camara/CampoDeFotos.d.ts +17 -4
- package/lib/typescript/src/camara/CampoDeFotos.d.ts.map +1 -1
- package/lib/typescript/src/camara/index.d.ts +1 -1
- package/lib/typescript/src/camara/index.d.ts.map +1 -1
- package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts +10 -0
- package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts.map +1 -1
- package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts +1 -1
- package/lib/typescript/src/formularios/campos.d.ts +43 -8
- package/lib/typescript/src/formularios/campos.d.ts.map +1 -1
- package/lib/typescript/src/hoja/HojaDeLista.d.ts.map +1 -1
- package/lib/typescript/src/hoja/HojaInferior.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/camara/CampoDeFotos.tsx +41 -12
- package/src/camara/index.ts +6 -1
- package/src/formularios/SelectorDeOpcion.tsx +40 -22
- package/src/formularios/TextoDeAccesoControlado.tsx +1 -1
- package/src/formularios/campos.tsx +276 -123
- package/src/hoja/HojaDeLista.tsx +15 -9
- package/src/hoja/HojaInferior.tsx +15 -9
- 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 {
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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`,
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
|
291
|
-
<
|
|
424
|
+
<Campo ayuda={ayuda} error={error}>
|
|
425
|
+
<CajaDeCampo
|
|
426
|
+
etiqueta={etiqueta}
|
|
292
427
|
onPress={() => setEligiendo(true)}
|
|
293
428
|
accessibilityRole="button"
|
|
294
|
-
|
|
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
|
-
</
|
|
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
|
|
412
|
-
<
|
|
413
|
-
{
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
449
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
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: {
|
|
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:
|
|
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
|
},
|
package/src/hoja/HojaDeLista.tsx
CHANGED
|
@@ -4,7 +4,8 @@ import BottomSheet, {
|
|
|
4
4
|
BottomSheetTextInput,
|
|
5
5
|
type BottomSheetBackdropProps,
|
|
6
6
|
} from "@gorhom/bottom-sheet";
|
|
7
|
-
import
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|