@acvl/mobile-components 0.0.0-stage → 0.0.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 +146 -2
- package/assets/fonts/Inter_400Regular.ttf +0 -0
- package/assets/fonts/Inter_500Medium.ttf +0 -0
- package/assets/fonts/Inter_600SemiBold.ttf +0 -0
- package/assets/fonts/Inter_700Bold.ttf +0 -0
- package/assets/fonts/Inter_800ExtraBold.ttf +0 -0
- package/assets/fonts/Inter_900Black.ttf +0 -0
- package/assets/fonts/OFL.txt +93 -0
- package/lib/module/componentes/AccionesDelEncabezado.js +212 -0
- package/lib/module/componentes/AccionesDelEncabezado.js.map +1 -0
- package/lib/module/componentes/BotonDeAvatar.js +55 -0
- package/lib/module/componentes/BotonDeAvatar.js.map +1 -0
- package/lib/module/componentes/BotonGrande.js +105 -0
- package/lib/module/componentes/BotonGrande.js.map +1 -0
- package/lib/module/componentes/CabeceraEmpujada.js +101 -0
- package/lib/module/componentes/CabeceraEmpujada.js.map +1 -0
- package/lib/module/componentes/Chip.js +77 -0
- package/lib/module/componentes/Chip.js.map +1 -0
- package/lib/module/componentes/ContenidoDePantalla.js +56 -0
- package/lib/module/componentes/ContenidoDePantalla.js.map +1 -0
- package/lib/module/componentes/EstadoVacio.js +65 -0
- package/lib/module/componentes/EstadoVacio.js.map +1 -0
- package/lib/module/componentes/Foto.js +106 -0
- package/lib/module/componentes/Foto.js.map +1 -0
- package/lib/module/componentes/IconoDeRenglon.js +44 -0
- package/lib/module/componentes/IconoDeRenglon.js.map +1 -0
- package/lib/module/componentes/Logo.js +44 -0
- package/lib/module/componentes/Logo.js.map +1 -0
- package/lib/module/componentes/Nota.js +51 -0
- package/lib/module/componentes/Nota.js.map +1 -0
- package/lib/module/componentes/OpcionesExcluyentes.js +69 -0
- package/lib/module/componentes/OpcionesExcluyentes.js.map +1 -0
- package/lib/module/componentes/PantallaDeError.js +84 -0
- package/lib/module/componentes/PantallaDeError.js.map +1 -0
- package/lib/module/componentes/Pestanas.js +87 -0
- package/lib/module/componentes/Pestanas.js.map +1 -0
- package/lib/module/componentes/Rotulo.js +28 -0
- package/lib/module/componentes/Rotulo.js.map +1 -0
- package/lib/module/componentes/RotuloDeSeccion.js +87 -0
- package/lib/module/componentes/RotuloDeSeccion.js.map +1 -0
- package/lib/module/componentes/Tarjeta.js +47 -0
- package/lib/module/componentes/Tarjeta.js.map +1 -0
- package/lib/module/componentes/TarjetaDeDatos.js +79 -0
- package/lib/module/componentes/TarjetaDeDatos.js.map +1 -0
- package/lib/module/componentes/TiraDeFotos.js +184 -0
- package/lib/module/componentes/TiraDeFotos.js.map +1 -0
- package/lib/module/componentes/VisorDeFoto.js +171 -0
- package/lib/module/componentes/VisorDeFoto.js.map +1 -0
- package/lib/module/componentes/logos/bola-blanca.png +0 -0
- package/lib/module/componentes/logos/bola.png +0 -0
- package/lib/module/componentes/logos/logo-blanco.png +0 -0
- package/lib/module/componentes/logos/logo.png +0 -0
- package/lib/module/dialogos/HostDeDialogos.js +170 -0
- package/lib/module/dialogos/HostDeDialogos.js.map +1 -0
- package/lib/module/dialogos/dialogos.js +112 -0
- package/lib/module/dialogos/dialogos.js.map +1 -0
- package/lib/module/formularios/CampoDeSeleccion.js +47 -0
- package/lib/module/formularios/CampoDeSeleccion.js.map +1 -0
- package/lib/module/formularios/SelectorDeOpcion.js +406 -0
- package/lib/module/formularios/SelectorDeOpcion.js.map +1 -0
- package/lib/module/formularios/TextoDeAccesoControlado.js +73 -0
- package/lib/module/formularios/TextoDeAccesoControlado.js.map +1 -0
- package/lib/module/formularios/campos.js +493 -0
- package/lib/module/formularios/campos.js.map +1 -0
- package/lib/module/formularios/controlados.js +197 -0
- package/lib/module/formularios/controlados.js.map +1 -0
- package/lib/module/formularios/errores.js +91 -0
- package/lib/module/formularios/errores.js.map +1 -0
- package/lib/module/formularios/guardado.js +78 -0
- package/lib/module/formularios/guardado.js.map +1 -0
- package/lib/module/formularios/useBusqueda.js +22 -0
- package/lib/module/formularios/useBusqueda.js.map +1 -0
- package/lib/module/fotos/elegir.js +178 -0
- package/lib/module/fotos/elegir.js.map +1 -0
- package/lib/module/fotos/index.js +8 -0
- package/lib/module/fotos/index.js.map +1 -0
- package/lib/module/fotos/reducir.js +93 -0
- package/lib/module/fotos/reducir.js.map +1 -0
- package/lib/module/hoja/HojaInferior.js +108 -0
- package/lib/module/hoja/HojaInferior.js.map +1 -0
- package/lib/module/hoja/index.js +7 -0
- package/lib/module/hoja/index.js.map +1 -0
- package/lib/module/hoja/useMasDeLaFicha.js +48 -0
- package/lib/module/hoja/useMasDeLaFicha.js.map +1 -0
- package/lib/module/index.js +66 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/listas/HojaDeFiltros.js +260 -0
- package/lib/module/listas/HojaDeFiltros.js.map +1 -0
- package/lib/module/listas/ListaPaginada.js +103 -0
- package/lib/module/listas/ListaPaginada.js.map +1 -0
- package/lib/module/listas/TarjetaDeslizable.js +118 -0
- package/lib/module/listas/TarjetaDeslizable.js.map +1 -0
- package/lib/module/listas/paginado.js +11 -0
- package/lib/module/listas/paginado.js.map +1 -0
- package/lib/module/listas/piezas.js +603 -0
- package/lib/module/listas/piezas.js.map +1 -0
- package/lib/module/navegacion/BarraDePestanas.js +162 -0
- package/lib/module/navegacion/BarraDePestanas.js.map +1 -0
- package/lib/module/navegacion/BotonDeBarra.js +58 -0
- package/lib/module/navegacion/BotonDeBarra.js.map +1 -0
- package/lib/module/navegacion/encabezado.js +103 -0
- package/lib/module/navegacion/encabezado.js.map +1 -0
- package/lib/module/pantallas/PantallaDeAcceso.js +195 -0
- package/lib/module/pantallas/PantallaDeAcceso.js.map +1 -0
- package/lib/module/pantallas/PantallaDeEdicion.js +121 -0
- package/lib/module/pantallas/PantallaDeEdicion.js.map +1 -0
- package/lib/module/pantallas/PantallaDeFicha.js +94 -0
- package/lib/module/pantallas/PantallaDeFicha.js.map +1 -0
- package/lib/module/pantallas/PantallaDePerfil.js +137 -0
- package/lib/module/pantallas/PantallaDePerfil.js.map +1 -0
- package/lib/module/tema/ProveedorDeTema.js +122 -0
- package/lib/module/tema/ProveedorDeTema.js.map +1 -0
- package/lib/module/tema/estilos.js +54 -0
- package/lib/module/tema/estilos.js.map +1 -0
- package/lib/module/tema/fuentes.js +28 -0
- package/lib/module/tema/fuentes.js.map +1 -0
- package/lib/module/tema/index.js +8 -0
- package/lib/module/tema/index.js.map +1 -0
- package/lib/module/tema/paleta.js +107 -0
- package/lib/module/tema/paleta.js.map +1 -0
- package/lib/module/tema/tipografia.js +32 -0
- package/lib/module/tema/tipografia.js.map +1 -0
- package/lib/module/texto.js +48 -0
- package/lib/module/texto.js.map +1 -0
- package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts +34 -0
- package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts.map +1 -0
- package/lib/typescript/src/componentes/BotonDeAvatar.d.ts +17 -0
- package/lib/typescript/src/componentes/BotonDeAvatar.d.ts.map +1 -0
- package/lib/typescript/src/componentes/BotonGrande.d.ts +26 -0
- package/lib/typescript/src/componentes/BotonGrande.d.ts.map +1 -0
- package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts +18 -0
- package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Chip.d.ts +14 -0
- package/lib/typescript/src/componentes/Chip.d.ts.map +1 -0
- package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts +17 -0
- package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts.map +1 -0
- package/lib/typescript/src/componentes/EstadoVacio.d.ts +17 -0
- package/lib/typescript/src/componentes/EstadoVacio.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Foto.d.ts +41 -0
- package/lib/typescript/src/componentes/Foto.d.ts.map +1 -0
- package/lib/typescript/src/componentes/IconoDeRenglon.d.ts +25 -0
- package/lib/typescript/src/componentes/IconoDeRenglon.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Logo.d.ts +13 -0
- package/lib/typescript/src/componentes/Logo.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Nota.d.ts +7 -0
- package/lib/typescript/src/componentes/Nota.d.ts.map +1 -0
- package/lib/typescript/src/componentes/OpcionesExcluyentes.d.ts +14 -0
- package/lib/typescript/src/componentes/OpcionesExcluyentes.d.ts.map +1 -0
- package/lib/typescript/src/componentes/PantallaDeError.d.ts +23 -0
- package/lib/typescript/src/componentes/PantallaDeError.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Pestanas.d.ts +28 -0
- package/lib/typescript/src/componentes/Pestanas.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Rotulo.d.ts +6 -0
- package/lib/typescript/src/componentes/Rotulo.d.ts.map +1 -0
- package/lib/typescript/src/componentes/RotuloDeSeccion.d.ts +20 -0
- package/lib/typescript/src/componentes/RotuloDeSeccion.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Tarjeta.d.ts +15 -0
- package/lib/typescript/src/componentes/Tarjeta.d.ts.map +1 -0
- package/lib/typescript/src/componentes/TarjetaDeDatos.d.ts +13 -0
- package/lib/typescript/src/componentes/TarjetaDeDatos.d.ts.map +1 -0
- package/lib/typescript/src/componentes/TiraDeFotos.d.ts +36 -0
- package/lib/typescript/src/componentes/TiraDeFotos.d.ts.map +1 -0
- package/lib/typescript/src/componentes/VisorDeFoto.d.ts +23 -0
- package/lib/typescript/src/componentes/VisorDeFoto.d.ts.map +1 -0
- package/lib/typescript/src/dialogos/HostDeDialogos.d.ts +14 -0
- package/lib/typescript/src/dialogos/HostDeDialogos.d.ts.map +1 -0
- package/lib/typescript/src/dialogos/dialogos.d.ts +65 -0
- package/lib/typescript/src/dialogos/dialogos.d.ts.map +1 -0
- package/lib/typescript/src/formularios/CampoDeSeleccion.d.ts +22 -0
- package/lib/typescript/src/formularios/CampoDeSeleccion.d.ts.map +1 -0
- package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts +59 -0
- package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts.map +1 -0
- package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts +18 -0
- package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts.map +1 -0
- package/lib/typescript/src/formularios/campos.d.ts +107 -0
- package/lib/typescript/src/formularios/campos.d.ts.map +1 -0
- package/lib/typescript/src/formularios/controlados.d.ts +29 -0
- package/lib/typescript/src/formularios/controlados.d.ts.map +1 -0
- package/lib/typescript/src/formularios/errores.d.ts +36 -0
- package/lib/typescript/src/formularios/errores.d.ts.map +1 -0
- package/lib/typescript/src/formularios/guardado.d.ts +36 -0
- package/lib/typescript/src/formularios/guardado.d.ts.map +1 -0
- package/lib/typescript/src/formularios/useBusqueda.d.ts +7 -0
- package/lib/typescript/src/formularios/useBusqueda.d.ts.map +1 -0
- package/lib/typescript/src/fotos/elegir.d.ts +58 -0
- package/lib/typescript/src/fotos/elegir.d.ts.map +1 -0
- package/lib/typescript/src/fotos/index.d.ts +3 -0
- package/lib/typescript/src/fotos/index.d.ts.map +1 -0
- package/lib/typescript/src/fotos/reducir.d.ts +32 -0
- package/lib/typescript/src/fotos/reducir.d.ts.map +1 -0
- package/lib/typescript/src/hoja/HojaInferior.d.ts +21 -0
- package/lib/typescript/src/hoja/HojaInferior.d.ts.map +1 -0
- package/lib/typescript/src/hoja/index.d.ts +3 -0
- package/lib/typescript/src/hoja/index.d.ts.map +1 -0
- package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts +27 -0
- package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +45 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/listas/HojaDeFiltros.d.ts +60 -0
- package/lib/typescript/src/listas/HojaDeFiltros.d.ts.map +1 -0
- package/lib/typescript/src/listas/ListaPaginada.d.ts +39 -0
- package/lib/typescript/src/listas/ListaPaginada.d.ts.map +1 -0
- package/lib/typescript/src/listas/TarjetaDeslizable.d.ts +33 -0
- package/lib/typescript/src/listas/TarjetaDeslizable.d.ts.map +1 -0
- package/lib/typescript/src/listas/paginado.d.ts +16 -0
- package/lib/typescript/src/listas/paginado.d.ts.map +1 -0
- package/lib/typescript/src/listas/piezas.d.ts +135 -0
- package/lib/typescript/src/listas/piezas.d.ts.map +1 -0
- package/lib/typescript/src/navegacion/BarraDePestanas.d.ts +57 -0
- package/lib/typescript/src/navegacion/BarraDePestanas.d.ts.map +1 -0
- package/lib/typescript/src/navegacion/BotonDeBarra.d.ts +15 -0
- package/lib/typescript/src/navegacion/BotonDeBarra.d.ts.map +1 -0
- package/lib/typescript/src/navegacion/encabezado.d.ts +44 -0
- package/lib/typescript/src/navegacion/encabezado.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts +32 -0
- package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts +38 -0
- package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts +35 -0
- package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts +19 -0
- package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts.map +1 -0
- package/lib/typescript/src/tema/ProveedorDeTema.d.ts +32 -0
- package/lib/typescript/src/tema/ProveedorDeTema.d.ts.map +1 -0
- package/lib/typescript/src/tema/estilos.d.ts +31 -0
- package/lib/typescript/src/tema/estilos.d.ts.map +1 -0
- package/lib/typescript/src/tema/fuentes.d.ts +20 -0
- package/lib/typescript/src/tema/fuentes.d.ts.map +1 -0
- package/lib/typescript/src/tema/index.d.ts +6 -0
- package/lib/typescript/src/tema/index.d.ts.map +1 -0
- package/lib/typescript/src/tema/paleta.d.ts +86 -0
- package/lib/typescript/src/tema/paleta.d.ts.map +1 -0
- package/lib/typescript/src/tema/tipografia.d.ts +29 -0
- package/lib/typescript/src/tema/tipografia.d.ts.map +1 -0
- package/lib/typescript/src/texto.d.ts +16 -0
- package/lib/typescript/src/texto.d.ts.map +1 -0
- package/package.json +135 -5
- package/src/componentes/AccionesDelEncabezado.tsx +247 -0
- package/src/componentes/BotonDeAvatar.tsx +59 -0
- package/src/componentes/BotonGrande.tsx +100 -0
- package/src/componentes/CabeceraEmpujada.tsx +69 -0
- package/src/componentes/Chip.tsx +60 -0
- package/src/componentes/ContenidoDePantalla.tsx +53 -0
- package/src/componentes/EstadoVacio.tsx +45 -0
- package/src/componentes/Foto.tsx +92 -0
- package/src/componentes/IconoDeRenglon.tsx +42 -0
- package/src/componentes/Logo.tsx +34 -0
- package/src/componentes/Nota.tsx +37 -0
- package/src/componentes/OpcionesExcluyentes.tsx +62 -0
- package/src/componentes/PantallaDeError.tsx +76 -0
- package/src/componentes/Pestanas.tsx +86 -0
- package/src/componentes/Rotulo.tsx +21 -0
- package/src/componentes/RotuloDeSeccion.tsx +71 -0
- package/src/componentes/Tarjeta.tsx +44 -0
- package/src/componentes/TarjetaDeDatos.tsx +62 -0
- package/src/componentes/TiraDeFotos.tsx +189 -0
- package/src/componentes/VisorDeFoto.tsx +181 -0
- package/src/componentes/logos/bola-blanca.png +0 -0
- package/src/componentes/logos/bola.png +0 -0
- package/src/componentes/logos/logo-blanco.png +0 -0
- package/src/componentes/logos/logo.png +0 -0
- package/src/dialogos/HostDeDialogos.tsx +172 -0
- package/src/dialogos/dialogos.ts +148 -0
- package/src/formularios/CampoDeSeleccion.tsx +46 -0
- package/src/formularios/SelectorDeOpcion.tsx +407 -0
- package/src/formularios/TextoDeAccesoControlado.tsx +72 -0
- package/src/formularios/campos.tsx +483 -0
- package/src/formularios/controlados.tsx +198 -0
- package/src/formularios/errores.ts +105 -0
- package/src/formularios/guardado.ts +76 -0
- package/src/formularios/useBusqueda.ts +19 -0
- package/src/fotos/elegir.ts +194 -0
- package/src/fotos/index.ts +15 -0
- package/src/fotos/reducir.ts +87 -0
- package/src/hoja/HojaInferior.tsx +102 -0
- package/src/hoja/index.ts +4 -0
- package/src/hoja/useMasDeLaFicha.tsx +63 -0
- package/src/index.ts +143 -0
- package/src/listas/HojaDeFiltros.tsx +261 -0
- package/src/listas/ListaPaginada.tsx +117 -0
- package/src/listas/TarjetaDeslizable.tsx +126 -0
- package/src/listas/paginado.ts +15 -0
- package/src/listas/piezas.tsx +594 -0
- package/src/navegacion/BarraDePestanas.tsx +198 -0
- package/src/navegacion/BotonDeBarra.tsx +61 -0
- package/src/navegacion/encabezado.tsx +88 -0
- package/src/pantallas/PantallaDeAcceso.tsx +184 -0
- package/src/pantallas/PantallaDeEdicion.tsx +116 -0
- package/src/pantallas/PantallaDeFicha.tsx +115 -0
- package/src/pantallas/PantallaDePerfil.tsx +126 -0
- package/src/tema/ProveedorDeTema.tsx +150 -0
- package/src/tema/estilos.ts +51 -0
- package/src/tema/fuentes.ts +20 -0
- package/src/tema/index.ts +10 -0
- package/src/tema/paleta.ts +147 -0
- package/src/tema/tipografia.ts +29 -0
- package/src/texto.tsx +44 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { ActivityIndicator, Image, type ImageProps, StyleSheet, View } from "react-native";
|
|
4
|
+
import { usePaleta } from "../tema";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Una foto: del servidor (`ruta`, la que da el API) o del teléfono (`uri` local, que todavía no
|
|
8
|
+
* sube).
|
|
9
|
+
*/
|
|
10
|
+
export type FotoAVer = { ruta: string } | { uri: string };
|
|
11
|
+
|
|
12
|
+
/** Cómo baja la app una foto del servidor: la `uri` local donde quedó. Rechaza si no se pudo. */
|
|
13
|
+
export type BajarFoto = (ruta: string) => Promise<string>;
|
|
14
|
+
|
|
15
|
+
const Bajar = React.createContext<BajarFoto | null>(null);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Cómo se bajan las fotos del servidor en esta app. Va en el layout raíz, una vez.
|
|
19
|
+
*
|
|
20
|
+
* Es de la app y no de la librería porque las fotos piden la sesión: en Android, con la nueva
|
|
21
|
+
* arquitectura, el `Image` de React Native **no manda las `headers` del `source`**, así que una
|
|
22
|
+
* foto con `Authorization` se baja primero con `fetch` a un archivo (con el refresh del token de
|
|
23
|
+
* cada app) y se pinta el archivo. Sin proveedor, la `ruta` se pinta tal cual, como URL pública.
|
|
24
|
+
*/
|
|
25
|
+
export function ProveedorDeFotos({
|
|
26
|
+
bajar,
|
|
27
|
+
children,
|
|
28
|
+
}: {
|
|
29
|
+
bajar: BajarFoto;
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
}) {
|
|
32
|
+
return <Bajar.Provider value={bajar}>{children}</Bajar.Provider>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Props extends Omit<ImageProps, "source"> {
|
|
36
|
+
foto: FotoAVer;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Pinta una foto, venga de donde venga: la misma en una tira, el visor o una lista.
|
|
41
|
+
*
|
|
42
|
+
* Las del servidor se bajan primero con el `ProveedorDeFotos` de la app y se pinta el archivo
|
|
43
|
+
* local. Mientras baja, un indicador; si no se pudo, un icono de foto rota en vez de un cuadro gris
|
|
44
|
+
* sin explicación.
|
|
45
|
+
*
|
|
46
|
+
* Una miniatura lleva `resizeMethod="resize"`: en Android, sin él, cada cuadrito de 72 px
|
|
47
|
+
* decodifica la foto entera de 1920 px en memoria, y con varias la lista se arrastra.
|
|
48
|
+
*/
|
|
49
|
+
const Foto = ({ foto, style, onError, ...props }: Props) => {
|
|
50
|
+
const c = usePaleta();
|
|
51
|
+
const bajar = React.useContext(Bajar);
|
|
52
|
+
const ruta = "ruta" in foto ? foto.ruta : null;
|
|
53
|
+
// Lo bajado, con la ruta a la que corresponde: si la foto cambia, lo de la anterior no sirve.
|
|
54
|
+
const [bajada, setBajada] = React.useState<{ ruta: string; uri?: string; fallo?: boolean }>();
|
|
55
|
+
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
if (!ruta || !bajar) return;
|
|
58
|
+
let vigente = true;
|
|
59
|
+
bajar(ruta)
|
|
60
|
+
.then((uri) => vigente && setBajada({ ruta, uri }))
|
|
61
|
+
.catch(() => vigente && setBajada({ ruta, fallo: true }));
|
|
62
|
+
return () => {
|
|
63
|
+
vigente = false;
|
|
64
|
+
};
|
|
65
|
+
}, [ruta, bajar]);
|
|
66
|
+
|
|
67
|
+
const deEstaRuta = bajada && bajada.ruta === ruta ? bajada : undefined;
|
|
68
|
+
const local = "uri" in foto ? foto.uri : bajar ? (deEstaRuta?.uri ?? null) : ruta;
|
|
69
|
+
const fallo = Boolean(deEstaRuta?.fallo);
|
|
70
|
+
|
|
71
|
+
if (fallo) {
|
|
72
|
+
return (
|
|
73
|
+
<View style={[style, styles.centro]}>
|
|
74
|
+
<MaterialCommunityIcons name="image-broken-variant" size={28} color={c.secondary} />
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (!local) {
|
|
79
|
+
return (
|
|
80
|
+
<View style={[style, styles.centro]}>
|
|
81
|
+
<ActivityIndicator color={c.secondary} />
|
|
82
|
+
</View>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return <Image {...props} style={style} source={{ uri: local }} onError={onError} />;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const styles = StyleSheet.create({
|
|
89
|
+
centro: { alignItems: "center", justifyContent: "center" },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export default Foto;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { renglon } from "../tema";
|
|
2
|
+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useWindowDimensions, View, type StyleProp, type ViewStyle } from "react-native";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
name: React.ComponentProps<typeof MaterialCommunityIcons>["name"];
|
|
8
|
+
color: string;
|
|
9
|
+
/** El `fontSize` del texto que acompaña. Su estilo tiene que llevar `...renglon(fontSize)`. */
|
|
10
|
+
fontSize: number;
|
|
11
|
+
/** El tamaño del icono a escala 1. Por omisión, el de la letra. */
|
|
12
|
+
size?: number;
|
|
13
|
+
style?: StyleProp<ViewStyle>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Un icono que acompaña a un renglón de texto y **se queda centrado en él aunque el operador
|
|
18
|
+
* agrande la letra del teléfono**.
|
|
19
|
+
*
|
|
20
|
+
* El texto crece con la escala del sistema (Ajustes › Tamaño de fuente; hay quien la trae al
|
|
21
|
+
* 180%) y un icono de tamaño fijo no: se quedaba chico y desfasado hacia arriba. Aquí el icono
|
|
22
|
+
* crece con la misma escala y va en una caja del alto del primer renglón, así que queda centrado
|
|
23
|
+
* contra la primera línea aunque el texto parta en varias. Para eso el texto necesita un alto de
|
|
24
|
+
* renglón explícito: el de `renglon()`, que es el mismo que usa esta caja.
|
|
25
|
+
*/
|
|
26
|
+
const IconoDeRenglon = ({ name, color, fontSize, size = fontSize, style }: Props) => {
|
|
27
|
+
const { fontScale } = useWindowDimensions();
|
|
28
|
+
const alto = renglon(fontSize).lineHeight * fontScale;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<View
|
|
32
|
+
style={[
|
|
33
|
+
{ height: alto, minWidth: alto, alignItems: "center", justifyContent: "center" },
|
|
34
|
+
style,
|
|
35
|
+
]}
|
|
36
|
+
>
|
|
37
|
+
<MaterialCommunityIcons name={name} size={size * fontScale} color={color} />
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default IconoDeRenglon;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Image } from "react-native";
|
|
2
|
+
import { useEsOscuro } from "../tema";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* La bola del header, de los archivos de marca (`O:\Logo ACV`): **a color** en claro
|
|
6
|
+
* (`bola.png`, el «standalone») y **toda en blanco** en oscuro (`bola-blanca.png`, sacada del
|
|
7
|
+
* mismo archivo: lo rojo opaco y los trazos huecos), como el `Logo` del portal en oscuro.
|
|
8
|
+
*/
|
|
9
|
+
export const Logo = () => {
|
|
10
|
+
const oscuro = useEsOscuro();
|
|
11
|
+
return (
|
|
12
|
+
<Image
|
|
13
|
+
source={oscuro ? require("./logos/bola-blanca.png") : require("./logos/bola.png")}
|
|
14
|
+
// 42 × 36 conserva el aspecto de los PNG (242 × 210).
|
|
15
|
+
style={{ width: 42, height: 36, resizeMode: "contain" }}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* El logo con las letras: el del login. En oscuro va **todo en blanco**, como el `Logo` del
|
|
22
|
+
* portal: `logo-blanco.png` sale del archivo de marca en blanco sobre negro
|
|
23
|
+
* (`O:\Logo ACV\acv-logo_2012-whitereversed`), con el negro vuelto transparente.
|
|
24
|
+
*/
|
|
25
|
+
export const LogoCompleto = () => {
|
|
26
|
+
const oscuro = useEsOscuro();
|
|
27
|
+
return (
|
|
28
|
+
<Image
|
|
29
|
+
source={oscuro ? require("./logos/logo-blanco.png") : require("./logos/logo.png")}
|
|
30
|
+
// 175 × 66 conserva el aspecto del PNG claro (318 × 120); el blanco cabe en la misma caja.
|
|
31
|
+
style={{ resizeMode: "contain", width: 175, height: 66 }}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import IconoDeRenglon from "../componentes/IconoDeRenglon";
|
|
2
|
+
import { Text } from "../texto";
|
|
3
|
+
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
4
|
+
import { View } from "react-native";
|
|
5
|
+
|
|
6
|
+
/** Un aviso dentro de la pantalla: azul para informar, ámbar para advertir. */
|
|
7
|
+
const Nota = ({ texto, tono = "info" }: { texto: string; tono?: "info" | "aviso" }) => {
|
|
8
|
+
const c = usePaleta();
|
|
9
|
+
const styles = useStyles();
|
|
10
|
+
const aviso = tono === "aviso";
|
|
11
|
+
return (
|
|
12
|
+
<View style={[styles.nota, aviso && styles.notaAviso]}>
|
|
13
|
+
<IconoDeRenglon
|
|
14
|
+
name={aviso ? "alert-outline" : "information-outline"}
|
|
15
|
+
fontSize={tipografia.cuerpo}
|
|
16
|
+
color={aviso ? c.aviso.tinta : c.info.tinta}
|
|
17
|
+
/>
|
|
18
|
+
<Text style={[styles.notaTexto, aviso && styles.notaTextoAviso]}>{texto}</Text>
|
|
19
|
+
</View>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const useStyles = crearEstilos((c) => ({
|
|
24
|
+
nota: {
|
|
25
|
+
flexDirection: "row",
|
|
26
|
+
alignItems: "flex-start",
|
|
27
|
+
gap: 8,
|
|
28
|
+
backgroundColor: c.info.fondo,
|
|
29
|
+
borderRadius: 12,
|
|
30
|
+
padding: 14,
|
|
31
|
+
},
|
|
32
|
+
notaAviso: { backgroundColor: c.aviso.fondo },
|
|
33
|
+
notaTexto: { flex: 1, ...renglon(tipografia.cuerpo), color: c.info.tinta },
|
|
34
|
+
notaTextoAviso: { color: c.aviso.tinta },
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
export default Nota;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import IconoDeRenglon from "../componentes/IconoDeRenglon";
|
|
2
|
+
import { Text } from "../texto";
|
|
3
|
+
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
4
|
+
import { Pressable, View } from "react-native";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Opciones excluyentes como tarjetas, una por renglón: la carga y la razón del cambio de sello en
|
|
8
|
+
* la caseta, la razón de un rechazo en la oficina.
|
|
9
|
+
*/
|
|
10
|
+
const OpcionesExcluyentes = <V extends string | boolean>({
|
|
11
|
+
opciones,
|
|
12
|
+
valor,
|
|
13
|
+
onElegir,
|
|
14
|
+
}: {
|
|
15
|
+
opciones: readonly { valor: V; texto: string }[];
|
|
16
|
+
valor: V | null;
|
|
17
|
+
onElegir: (valor: V) => void;
|
|
18
|
+
}) => {
|
|
19
|
+
const c = usePaleta();
|
|
20
|
+
const styles = useStyles();
|
|
21
|
+
return (
|
|
22
|
+
<View style={styles.opciones}>
|
|
23
|
+
{opciones.map((opcion) => {
|
|
24
|
+
const elegida = opcion.valor === valor;
|
|
25
|
+
return (
|
|
26
|
+
<Pressable
|
|
27
|
+
key={String(opcion.valor)}
|
|
28
|
+
onPress={() => onElegir(opcion.valor)}
|
|
29
|
+
accessibilityRole="radio"
|
|
30
|
+
accessibilityState={{ selected: elegida }}
|
|
31
|
+
style={[styles.opcion, elegida && styles.opcionElegida]}
|
|
32
|
+
>
|
|
33
|
+
<IconoDeRenglon
|
|
34
|
+
name={elegida ? "radiobox-marked" : "radiobox-blank"}
|
|
35
|
+
fontSize={tipografia.subtitulo}
|
|
36
|
+
color={elegida ? c.primary : c.secondaryLight}
|
|
37
|
+
/>
|
|
38
|
+
<Text style={styles.opcionTexto}>{opcion.texto}</Text>
|
|
39
|
+
</Pressable>
|
|
40
|
+
);
|
|
41
|
+
})}
|
|
42
|
+
</View>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const useStyles = crearEstilos((c) => ({
|
|
47
|
+
opciones: { gap: 10 },
|
|
48
|
+
opcion: {
|
|
49
|
+
flexDirection: "row",
|
|
50
|
+
alignItems: "flex-start",
|
|
51
|
+
gap: 8,
|
|
52
|
+
backgroundColor: c.superficie,
|
|
53
|
+
borderRadius: 14,
|
|
54
|
+
borderWidth: 2,
|
|
55
|
+
borderColor: c.secondaryLighter,
|
|
56
|
+
padding: 14,
|
|
57
|
+
},
|
|
58
|
+
opcionElegida: { borderColor: c.primary, backgroundColor: c.primaryWash },
|
|
59
|
+
opcionTexto: { flex: 1, ...renglon(tipografia.subtitulo), fontWeight: "800", color: c.texto },
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
export default OpcionesExcluyentes;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { ScrollView } from "react-native";
|
|
4
|
+
import { SafeAreaView } from "react-native-safe-area-context";
|
|
5
|
+
import { Text } from "../texto";
|
|
6
|
+
import { crearEstilos, tipografia, usePaleta } from "../tema";
|
|
7
|
+
import BotonGrande from "./BotonGrande";
|
|
8
|
+
|
|
9
|
+
/** Lo que expo-router le pasa al `ErrorBoundary` de un layout. */
|
|
10
|
+
export interface PropsDePantallaDeError {
|
|
11
|
+
error: Error;
|
|
12
|
+
retry: () => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lo que se ve cuando una pantalla truena al pintarse, en vez de la pantalla roja (en desarrollo) o
|
|
17
|
+
* de una app que se cierra sola (en producción). Es el `ErrorBoundary` de expo-router: lo exporta el
|
|
18
|
+
* layout raíz, envuelto para pasarle `onError`.
|
|
19
|
+
*
|
|
20
|
+
* El error se reporta aquí (`onError`, a Sentry): React no lo deja llegar al manejador global una
|
|
21
|
+
* vez que una frontera lo atrapa. «Reintentar» vuelve a montar la pantalla.
|
|
22
|
+
*
|
|
23
|
+
* No usa nada de Paper ni de un proveedor: si lo que tronó es el layout raíz, aquí no hay
|
|
24
|
+
* proveedores.
|
|
25
|
+
*/
|
|
26
|
+
const PantallaDeError = ({
|
|
27
|
+
error,
|
|
28
|
+
retry,
|
|
29
|
+
onError,
|
|
30
|
+
mensaje = "Esta pantalla no se pudo abrir. Intenta de nuevo; si vuelve a pasar, avisa a soporte.",
|
|
31
|
+
}: PropsDePantallaDeError & {
|
|
32
|
+
onError?: (error: Error) => void;
|
|
33
|
+
/** Lo que se le dice a quien la ve: qué no se perdió y a quién avisar. */
|
|
34
|
+
mensaje?: string;
|
|
35
|
+
}) => {
|
|
36
|
+
const c = usePaleta();
|
|
37
|
+
const styles = useStyles();
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
onError?.(error);
|
|
40
|
+
// Se reporta una vez por error, aunque quien llama pase un `onError` nuevo en cada render.
|
|
41
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
+
}, [error]);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<SafeAreaView style={styles.root}>
|
|
46
|
+
<ScrollView contentContainerStyle={styles.contenido}>
|
|
47
|
+
<MaterialCommunityIcons name="alert-octagon-outline" size={64} color={c.primary} />
|
|
48
|
+
<Text style={styles.titulo}>Algo salió mal</Text>
|
|
49
|
+
<Text style={styles.texto}>{mensaje}</Text>
|
|
50
|
+
<BotonGrande icono="refresh" onPress={() => void retry()}>
|
|
51
|
+
Reintentar
|
|
52
|
+
</BotonGrande>
|
|
53
|
+
</ScrollView>
|
|
54
|
+
</SafeAreaView>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const useStyles = crearEstilos((c) => ({
|
|
59
|
+
root: { flex: 1, backgroundColor: c.fondo },
|
|
60
|
+
contenido: {
|
|
61
|
+
flexGrow: 1,
|
|
62
|
+
justifyContent: "center",
|
|
63
|
+
alignItems: "stretch",
|
|
64
|
+
gap: 16,
|
|
65
|
+
padding: 24,
|
|
66
|
+
},
|
|
67
|
+
titulo: {
|
|
68
|
+
fontSize: tipografia.titulo,
|
|
69
|
+
fontWeight: "800",
|
|
70
|
+
color: c.secondaryDark,
|
|
71
|
+
textAlign: "center",
|
|
72
|
+
},
|
|
73
|
+
texto: { fontSize: tipografia.cuerpo, color: c.secondary, textAlign: "center" },
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
export default PantallaDeError;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
2
|
+
import { Text } from "../texto";
|
|
3
|
+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { Pressable, View } from "react-native";
|
|
6
|
+
|
|
7
|
+
export interface Pestana<K extends string> {
|
|
8
|
+
key: K;
|
|
9
|
+
label: string;
|
|
10
|
+
icono: React.ComponentProps<typeof MaterialCommunityIcons>["name"];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Props<K extends string> {
|
|
14
|
+
pestanas: readonly Pestana<K>[];
|
|
15
|
+
activa: K;
|
|
16
|
+
onSelect: (key: K) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* La tira de pestañas dentro de una pantalla: la misma de las listas de acv-wms (Cobranza,
|
|
21
|
+
* Importaciones), con su icono, y la elegida en rojo sobre un lavado rojo. Allá se repite en
|
|
22
|
+
* cada pantalla; aquí es un componente para que cada sección nueva la reutilice.
|
|
23
|
+
*
|
|
24
|
+
* Los iconos son de MaterialCommunityIcons, la familia de Paper, y no de MaterialIcons como en
|
|
25
|
+
* wms: es la que tiene `progress-clock`, el de Actual.
|
|
26
|
+
*
|
|
27
|
+
* **El icono va arriba del texto**, no al lado como en wms: con la letra del operador
|
|
28
|
+
* (`tipografia.etiqueta`) tres rótulos en fila no caben en un teléfono, y el botón más alto
|
|
29
|
+
* también se atina mejor con prisa. El rótulo no se encoge por debajo de `etiqueta`, que es el
|
|
30
|
+
* mínimo: con la letra muy grande, parte en otro renglón.
|
|
31
|
+
*/
|
|
32
|
+
const Pestanas = <K extends string>({ pestanas, activa, onSelect }: Props<K>) => {
|
|
33
|
+
const c = usePaleta();
|
|
34
|
+
const styles = useStyles();
|
|
35
|
+
return (
|
|
36
|
+
<View style={styles.tabs}>
|
|
37
|
+
{pestanas.map((p) => {
|
|
38
|
+
const on = p.key === activa;
|
|
39
|
+
return (
|
|
40
|
+
<Pressable
|
|
41
|
+
key={p.key}
|
|
42
|
+
onPress={() => onSelect(p.key)}
|
|
43
|
+
accessibilityRole="tab"
|
|
44
|
+
accessibilityState={{ selected: on }}
|
|
45
|
+
style={[styles.tab, on && styles.tabOn]}
|
|
46
|
+
>
|
|
47
|
+
<MaterialCommunityIcons
|
|
48
|
+
name={p.icono}
|
|
49
|
+
size={22}
|
|
50
|
+
color={on ? c.primary : c.secondaryLight}
|
|
51
|
+
/>
|
|
52
|
+
<Text style={[styles.tabText, on && styles.tabTextOn]}>{p.label}</Text>
|
|
53
|
+
</Pressable>
|
|
54
|
+
);
|
|
55
|
+
})}
|
|
56
|
+
</View>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const useStyles = crearEstilos((c) => ({
|
|
61
|
+
tabs: { flexDirection: "row", gap: 6, marginBottom: 12 },
|
|
62
|
+
tab: {
|
|
63
|
+
flex: 1,
|
|
64
|
+
alignItems: "center",
|
|
65
|
+
justifyContent: "center",
|
|
66
|
+
gap: 2,
|
|
67
|
+
paddingVertical: 8,
|
|
68
|
+
paddingHorizontal: 6,
|
|
69
|
+
borderRadius: 10,
|
|
70
|
+
borderWidth: 1.5,
|
|
71
|
+
borderColor: "transparent",
|
|
72
|
+
backgroundColor: c.superficie,
|
|
73
|
+
},
|
|
74
|
+
tabOn: { borderColor: c.primary, backgroundColor: c.primaryWash },
|
|
75
|
+
tabText: {
|
|
76
|
+
...renglon(tipografia.etiqueta),
|
|
77
|
+
fontWeight: "500",
|
|
78
|
+
color: c.secondaryLight,
|
|
79
|
+
textAlign: "center",
|
|
80
|
+
},
|
|
81
|
+
// Sólo el color, sin negritas: en negritas «Por iniciar» ya no cabía en un renglón y la pestaña
|
|
82
|
+
// crecía al elegirla. Lo elegido ya lo dicen el borde y el fondo.
|
|
83
|
+
tabTextOn: { color: c.primary },
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
export default Pestanas;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Text } from "../texto";
|
|
2
|
+
import { crearEstilos, renglon, tipografia } from "../tema";
|
|
3
|
+
|
|
4
|
+
/** El rótulo de una sección de un formulario, en mayúsculas: «Referencias», «Sello». */
|
|
5
|
+
const Rotulo = ({ children }: { children: string }) => {
|
|
6
|
+
const styles = useStyles();
|
|
7
|
+
return <Text style={styles.rotulo}>{children}</Text>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const useStyles = crearEstilos((c) => ({
|
|
11
|
+
rotulo: {
|
|
12
|
+
...renglon(tipografia.etiqueta),
|
|
13
|
+
fontWeight: "800",
|
|
14
|
+
color: c.secondary,
|
|
15
|
+
textTransform: "uppercase",
|
|
16
|
+
letterSpacing: 0.8,
|
|
17
|
+
marginTop: 8,
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
export default Rotulo;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
2
|
+
import { Text } from "../texto";
|
|
3
|
+
import { Pressable, useWindowDimensions, View } from "react-native";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: string;
|
|
7
|
+
/** El número de la sección, en un círculo rojo (gris si es cero). Sin él, sólo el rótulo. */
|
|
8
|
+
cuantas?: number;
|
|
9
|
+
/**
|
|
10
|
+
* La puerta a lo que la sección resume, a la derecha: «Editar ›», «Ver todos ›». Va en el rótulo
|
|
11
|
+
* y no como botón aparte para que no le robe un renglón a la lista.
|
|
12
|
+
*/
|
|
13
|
+
accion?: { texto: string; onPress: () => void };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* El rótulo de una sección de lista —«EN CURSO», «PENDIENTES 3»—, el `SectionLabel` de las
|
|
18
|
+
* revisiones de wms con la letra grande de las apps.
|
|
19
|
+
*/
|
|
20
|
+
const RotuloDeSeccion = ({ children, cuantas, accion }: Props) => {
|
|
21
|
+
const c = usePaleta();
|
|
22
|
+
const styles = useStyles();
|
|
23
|
+
// El círculo crece con la letra del sistema, igual que el número que lleva adentro.
|
|
24
|
+
const { fontScale } = useWindowDimensions();
|
|
25
|
+
const alto = renglon(tipografia.etiqueta).lineHeight * fontScale;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View style={styles.root}>
|
|
29
|
+
<Text style={styles.texto}>{children}</Text>
|
|
30
|
+
{cuantas != null ? (
|
|
31
|
+
<View
|
|
32
|
+
style={[
|
|
33
|
+
styles.cuenta,
|
|
34
|
+
{ minWidth: alto, height: alto, borderRadius: alto / 2 },
|
|
35
|
+
{ backgroundColor: cuantas > 0 ? c.primary : c.secondaryLight },
|
|
36
|
+
]}
|
|
37
|
+
>
|
|
38
|
+
<Text style={styles.cuentaTexto}>{cuantas}</Text>
|
|
39
|
+
</View>
|
|
40
|
+
) : null}
|
|
41
|
+
{accion ? (
|
|
42
|
+
<Pressable
|
|
43
|
+
onPress={accion.onPress}
|
|
44
|
+
accessibilityRole="button"
|
|
45
|
+
hitSlop={8}
|
|
46
|
+
style={styles.accion}
|
|
47
|
+
>
|
|
48
|
+
<Text style={styles.accionTexto}>{accion.texto} ›</Text>
|
|
49
|
+
</Pressable>
|
|
50
|
+
) : null}
|
|
51
|
+
</View>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const useStyles = crearEstilos((c) => ({
|
|
56
|
+
root: { flexDirection: "row", alignItems: "center", gap: 8, flexWrap: "wrap" },
|
|
57
|
+
texto: {
|
|
58
|
+
...renglon(tipografia.etiqueta),
|
|
59
|
+
color: c.texto,
|
|
60
|
+
textTransform: "uppercase",
|
|
61
|
+
letterSpacing: 1.4,
|
|
62
|
+
fontWeight: "800",
|
|
63
|
+
},
|
|
64
|
+
cuenta: { paddingHorizontal: 6, alignItems: "center", justifyContent: "center" },
|
|
65
|
+
cuentaTexto: { ...renglon(tipografia.etiqueta), color: c.sobrePrimario, fontWeight: "800" },
|
|
66
|
+
// `marginLeft: auto` la manda a la orilla derecha sin un separador que mida nada.
|
|
67
|
+
accion: { marginLeft: "auto" },
|
|
68
|
+
accionTexto: { ...renglon(tipografia.etiqueta), color: c.primary, fontWeight: "800" },
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
export default RotuloDeSeccion;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Pressable, View, type StyleProp, type ViewStyle } from "react-native";
|
|
2
|
+
import { crearEstilos } from "../tema";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* La tarjeta de la casa, sola: la superficie, sus esquinas y su aire, con lo que se le ponga
|
|
6
|
+
* adentro. Es de lo que están hechas `TarjetaDeLista` y las fichas; una pantalla la usa para lo que
|
|
7
|
+
* no es un renglón de lista —un bloque de fotos, el resumen de un cobro, un grupo de campos—.
|
|
8
|
+
*
|
|
9
|
+
* **Sin sombra**: en oscuro casi no se ve, así que lo que separa la tarjeta del fondo es el color
|
|
10
|
+
* de la superficie y, en oscuro, un borde. Con `onPress` se toca y se atenúa al presionarla.
|
|
11
|
+
*/
|
|
12
|
+
export default function Tarjeta({
|
|
13
|
+
children,
|
|
14
|
+
onPress,
|
|
15
|
+
style,
|
|
16
|
+
}: {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
onPress?: () => void;
|
|
19
|
+
style?: StyleProp<ViewStyle>;
|
|
20
|
+
}) {
|
|
21
|
+
const styles = useStyles();
|
|
22
|
+
if (!onPress) return <View style={[styles.tarjeta, style]}>{children}</View>;
|
|
23
|
+
return (
|
|
24
|
+
<Pressable
|
|
25
|
+
onPress={onPress}
|
|
26
|
+
accessibilityRole="button"
|
|
27
|
+
style={({ pressed }) => [styles.tarjeta, style, pressed && styles.presionada]}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</Pressable>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const useStyles = crearEstilos((c) => ({
|
|
35
|
+
tarjeta: {
|
|
36
|
+
backgroundColor: c.superficie,
|
|
37
|
+
borderRadius: 14,
|
|
38
|
+
padding: 14,
|
|
39
|
+
gap: 4,
|
|
40
|
+
borderWidth: c.esOscuro ? 1 : 0,
|
|
41
|
+
borderColor: c.secondaryLighter,
|
|
42
|
+
},
|
|
43
|
+
presionada: { opacity: 0.7 },
|
|
44
|
+
}));
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Text } from "../texto";
|
|
2
|
+
import { crearEstilos, renglon, tipografia } from "../tema";
|
|
3
|
+
import { useWindowDimensions, View } from "react-native";
|
|
4
|
+
|
|
5
|
+
/** Desde qué ancho los datos van a dos columnas: una tableta. */
|
|
6
|
+
const ANCHO_A_DOS_COLUMNAS = 700;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Datos de solo lectura, cada uno con su rótulo arriba: la ficha de una estancia de caseta, la de
|
|
10
|
+
* un viaje o un tracto en la oficina.
|
|
11
|
+
*
|
|
12
|
+
* **En una pantalla ancha van a dos columnas**; en un teléfono, uno debajo del otro. Los vacíos no
|
|
13
|
+
* se pintan. `atenuada` es la de algo que ya no cuenta (un movimiento anulado).
|
|
14
|
+
*/
|
|
15
|
+
const TarjetaDeDatos = ({
|
|
16
|
+
datos,
|
|
17
|
+
atenuada,
|
|
18
|
+
}: {
|
|
19
|
+
datos: [string, string | null | undefined][];
|
|
20
|
+
atenuada?: boolean;
|
|
21
|
+
}) => {
|
|
22
|
+
const styles = useStyles();
|
|
23
|
+
const { width } = useWindowDimensions();
|
|
24
|
+
const dosColumnas = width >= ANCHO_A_DOS_COLUMNAS;
|
|
25
|
+
return (
|
|
26
|
+
<View
|
|
27
|
+
style={[
|
|
28
|
+
styles.tarjeta,
|
|
29
|
+
dosColumnas && styles.tarjetaAncha,
|
|
30
|
+
atenuada && styles.atenuada,
|
|
31
|
+
]}
|
|
32
|
+
>
|
|
33
|
+
{datos
|
|
34
|
+
.filter(([, valor]) => valor)
|
|
35
|
+
.map(([etiqueta, valor]) => (
|
|
36
|
+
<View key={etiqueta} style={[styles.dato, dosColumnas && styles.datoEnColumna]}>
|
|
37
|
+
<Text style={styles.etiquetaDeDato}>{etiqueta}</Text>
|
|
38
|
+
<Text style={styles.valor}>{valor}</Text>
|
|
39
|
+
</View>
|
|
40
|
+
))}
|
|
41
|
+
</View>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const useStyles = crearEstilos((c) => ({
|
|
46
|
+
tarjeta: { backgroundColor: c.superficie, borderRadius: 14, padding: 14, gap: 10 },
|
|
47
|
+
tarjetaAncha: { flexDirection: "row", flexWrap: "wrap", columnGap: 24, rowGap: 12 },
|
|
48
|
+
atenuada: { opacity: 0.55 },
|
|
49
|
+
dato: { gap: 2 },
|
|
50
|
+
// Dos por renglón: la mitad del ancho menos la mitad del espacio entre columnas.
|
|
51
|
+
datoEnColumna: { width: "48%", flexGrow: 1 },
|
|
52
|
+
etiquetaDeDato: {
|
|
53
|
+
...renglon(tipografia.etiqueta),
|
|
54
|
+
fontWeight: "800",
|
|
55
|
+
color: c.secondary,
|
|
56
|
+
textTransform: "uppercase",
|
|
57
|
+
letterSpacing: 0.8,
|
|
58
|
+
},
|
|
59
|
+
valor: { ...renglon(tipografia.subtitulo), fontWeight: "800", color: c.texto },
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
export default TarjetaDeDatos;
|