@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,247 @@
|
|
|
1
|
+
import { Text } from "../texto";
|
|
2
|
+
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
3
|
+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import {
|
|
6
|
+
ActivityIndicator,
|
|
7
|
+
BackHandler,
|
|
8
|
+
Pressable,
|
|
9
|
+
useWindowDimensions,
|
|
10
|
+
View,
|
|
11
|
+
type LayoutChangeEvent,
|
|
12
|
+
} from "react-native";
|
|
13
|
+
import { Menu } from "react-native-paper";
|
|
14
|
+
|
|
15
|
+
/** Lo que mide un botón y lo que se separan: con eso se cuenta cuántos caben. */
|
|
16
|
+
const LADO = 40;
|
|
17
|
+
const ENTRE = 8;
|
|
18
|
+
/** Los que se ven a lo más, aunque quepan más: más botones ya no se distinguen de un vistazo. */
|
|
19
|
+
const MAXIMO_A_LA_VISTA = 3;
|
|
20
|
+
|
|
21
|
+
export interface AccionDelEncabezado {
|
|
22
|
+
/** El texto del renglón del menú y lo que lee el lector de pantalla. */
|
|
23
|
+
etiqueta: string;
|
|
24
|
+
icono: React.ComponentProps<typeof MaterialCommunityIcons>["name"];
|
|
25
|
+
onPress: () => void;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** Un indicador en vez del icono, y la acción bloqueada mientras corre. */
|
|
28
|
+
cargando?: boolean;
|
|
29
|
+
/** Para la que borra o cancela: en rojo, en el botón y en el menú. */
|
|
30
|
+
destructiva?: boolean;
|
|
31
|
+
/** Las fijadas son las últimas en irse al menú (Editar). */
|
|
32
|
+
fijada?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Una cifra pendiente de la acción: los mensajes sin leer de un hilo. Va sobre su botón o, si
|
|
35
|
+
* la acción se fue al menú, en su renglón — y entonces los tres puntos la suman, porque una
|
|
36
|
+
* cifra escondida no avisa de nada.
|
|
37
|
+
*/
|
|
38
|
+
insignia?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* La cifra sobre un botón: «9+» de diez para arriba, que más dígitos no caben en el círculo. Con la
|
|
43
|
+
* letra de la casa (`etiqueta`) y el círculo crece con la del sistema, como el de `RotuloDeSeccion`.
|
|
44
|
+
*/
|
|
45
|
+
const Insignia = ({ cuantas }: { cuantas: number }) => {
|
|
46
|
+
const styles = useStyles();
|
|
47
|
+
const { fontScale } = useWindowDimensions();
|
|
48
|
+
const alto = renglon(tipografia.etiqueta).lineHeight * fontScale;
|
|
49
|
+
return (
|
|
50
|
+
<View
|
|
51
|
+
style={[styles.insignia, { minWidth: alto, height: alto, borderRadius: alto / 2 }]}
|
|
52
|
+
pointerEvents="none"
|
|
53
|
+
>
|
|
54
|
+
<Text style={styles.insigniaTexto}>{cuantas > 9 ? "9+" : cuantas}</Text>
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const Boton = ({
|
|
60
|
+
accion,
|
|
61
|
+
onPress,
|
|
62
|
+
}: {
|
|
63
|
+
accion: Pick<
|
|
64
|
+
AccionDelEncabezado,
|
|
65
|
+
"etiqueta" | "icono" | "disabled" | "cargando" | "destructiva" | "insignia"
|
|
66
|
+
>;
|
|
67
|
+
onPress: () => void;
|
|
68
|
+
}) => {
|
|
69
|
+
const c = usePaleta();
|
|
70
|
+
const styles = useStyles();
|
|
71
|
+
return (
|
|
72
|
+
<Pressable
|
|
73
|
+
onPress={onPress}
|
|
74
|
+
disabled={accion.disabled || accion.cargando}
|
|
75
|
+
accessibilityRole="button"
|
|
76
|
+
accessibilityLabel={accion.etiqueta}
|
|
77
|
+
hitSlop={4}
|
|
78
|
+
style={({ pressed }) => [
|
|
79
|
+
styles.boton,
|
|
80
|
+
accion.disabled && styles.apagado,
|
|
81
|
+
pressed && styles.presionado,
|
|
82
|
+
]}
|
|
83
|
+
>
|
|
84
|
+
{accion.cargando ? (
|
|
85
|
+
<ActivityIndicator size="small" color={c.primary} />
|
|
86
|
+
) : (
|
|
87
|
+
<MaterialCommunityIcons
|
|
88
|
+
name={accion.icono}
|
|
89
|
+
size={22}
|
|
90
|
+
color={accion.destructiva ? c.primary : c.secondaryDark}
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
{accion.insignia ? <Insignia cuantas={accion.insignia} /> : null}
|
|
94
|
+
</Pressable>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Las acciones de una ficha en su encabezado: **las que quepan como botones y las demás en el menú
|
|
100
|
+
* de los tres puntos**. Es el `ResponsiveActions` de la app de acv-wms con la paleta de la casa.
|
|
101
|
+
*
|
|
102
|
+
* No mide los botones: mide el hueco que le deja el título (`onLayout`) y cuenta cuántos de
|
|
103
|
+
* `LADO` caben. Si caben todas, van todas; si no, una menos y el menú. Las `fijadas` son las
|
|
104
|
+
* últimas en irse, sin cambiar el orden en que se ven. Quien la pone le da al título
|
|
105
|
+
* `flexShrink: 1` y no `flex: 1`, para que le deje espacio.
|
|
106
|
+
*/
|
|
107
|
+
export default function AccionesDelEncabezado({ acciones }: { acciones: AccionDelEncabezado[] }) {
|
|
108
|
+
const c = usePaleta();
|
|
109
|
+
const styles = useStyles();
|
|
110
|
+
const [hueco, setHueco] = React.useState<number | null>(null);
|
|
111
|
+
const [menuAbierto, setMenuAbierto] = React.useState(false);
|
|
112
|
+
|
|
113
|
+
// Atrás con el menú abierto sólo lo cierra: si no, salía de la pantalla con el menú encima.
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
if (!menuAbierto) return;
|
|
116
|
+
const sub = BackHandler.addEventListener("hardwareBackPress", () => {
|
|
117
|
+
setMenuAbierto(false);
|
|
118
|
+
return true;
|
|
119
|
+
});
|
|
120
|
+
return () => sub.remove();
|
|
121
|
+
}, [menuAbierto]);
|
|
122
|
+
|
|
123
|
+
const medir = (e: LayoutChangeEvent) => {
|
|
124
|
+
const ancho = e.nativeEvent.layout.width;
|
|
125
|
+
if (ancho === hueco) return;
|
|
126
|
+
setHueco(ancho);
|
|
127
|
+
setMenuAbierto(false);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (!acciones.length) return null;
|
|
131
|
+
|
|
132
|
+
const caben = hueco === null ? 0 : Math.floor((hueco + ENTRE) / (LADO + ENTRE));
|
|
133
|
+
const todas = acciones.length <= Math.min(caben, MAXIMO_A_LA_VISTA);
|
|
134
|
+
const botones = todas ? acciones.length : Math.max(Math.min(caben - 1, MAXIMO_A_LA_VISTA), 0);
|
|
135
|
+
const aLaVista = new Set(
|
|
136
|
+
[...acciones]
|
|
137
|
+
.sort((a, b) => Number(Boolean(b.fijada)) - Number(Boolean(a.fijada)))
|
|
138
|
+
.slice(0, botones),
|
|
139
|
+
);
|
|
140
|
+
const visibles = acciones.filter((a) => aLaVista.has(a));
|
|
141
|
+
const enElMenu = acciones.filter((a) => !aLaVista.has(a));
|
|
142
|
+
const escondidas = enElMenu.reduce((suma, a) => suma + (a.insignia ?? 0), 0);
|
|
143
|
+
const cerrar = () => setMenuAbierto(false);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<View style={styles.barra} onLayout={medir}>
|
|
147
|
+
{hueco !== null ? (
|
|
148
|
+
<>
|
|
149
|
+
{visibles.map((a) => (
|
|
150
|
+
<Boton key={a.etiqueta} accion={a} onPress={a.onPress} />
|
|
151
|
+
))}
|
|
152
|
+
{enElMenu.length ? (
|
|
153
|
+
<Menu
|
|
154
|
+
visible={menuAbierto}
|
|
155
|
+
onDismiss={cerrar}
|
|
156
|
+
anchorPosition="bottom"
|
|
157
|
+
// Edge-to-edge: sin esto Paper cuenta dos veces la barra de estado.
|
|
158
|
+
statusBarHeight={0}
|
|
159
|
+
contentStyle={styles.menu}
|
|
160
|
+
anchor={
|
|
161
|
+
<Boton
|
|
162
|
+
accion={{
|
|
163
|
+
etiqueta: "Más acciones",
|
|
164
|
+
icono: "dots-vertical",
|
|
165
|
+
insignia: escondidas,
|
|
166
|
+
}}
|
|
167
|
+
onPress={() => setMenuAbierto(true)}
|
|
168
|
+
/>
|
|
169
|
+
}
|
|
170
|
+
>
|
|
171
|
+
{enElMenu.map((a) => (
|
|
172
|
+
<Menu.Item
|
|
173
|
+
key={a.etiqueta}
|
|
174
|
+
title={
|
|
175
|
+
<Text
|
|
176
|
+
style={[styles.menuTexto, a.destructiva && styles.rojo]}
|
|
177
|
+
>
|
|
178
|
+
{a.etiqueta}
|
|
179
|
+
{a.insignia ? ` (${a.insignia})` : ""}
|
|
180
|
+
</Text>
|
|
181
|
+
}
|
|
182
|
+
disabled={a.disabled || a.cargando}
|
|
183
|
+
leadingIcon={() =>
|
|
184
|
+
a.cargando ? (
|
|
185
|
+
<ActivityIndicator size="small" color={c.primary} />
|
|
186
|
+
) : (
|
|
187
|
+
<MaterialCommunityIcons
|
|
188
|
+
name={a.icono}
|
|
189
|
+
size={22}
|
|
190
|
+
color={a.destructiva ? c.primary : c.secondaryDark}
|
|
191
|
+
/>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
onPress={() => {
|
|
195
|
+
cerrar();
|
|
196
|
+
a.onPress();
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
))}
|
|
200
|
+
</Menu>
|
|
201
|
+
) : null}
|
|
202
|
+
</>
|
|
203
|
+
) : null}
|
|
204
|
+
</View>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const useStyles = crearEstilos((c) => ({
|
|
209
|
+
barra: {
|
|
210
|
+
flexGrow: 1,
|
|
211
|
+
flexBasis: 0,
|
|
212
|
+
minWidth: LADO,
|
|
213
|
+
flexDirection: "row",
|
|
214
|
+
justifyContent: "flex-end",
|
|
215
|
+
gap: ENTRE,
|
|
216
|
+
alignSelf: "center",
|
|
217
|
+
},
|
|
218
|
+
boton: {
|
|
219
|
+
width: LADO,
|
|
220
|
+
height: LADO,
|
|
221
|
+
borderRadius: LADO / 2,
|
|
222
|
+
borderWidth: 1.5,
|
|
223
|
+
borderColor: c.secondaryLighter,
|
|
224
|
+
backgroundColor: c.superficieTenue,
|
|
225
|
+
alignItems: "center",
|
|
226
|
+
justifyContent: "center",
|
|
227
|
+
},
|
|
228
|
+
apagado: { opacity: 0.45 },
|
|
229
|
+
presionado: { opacity: 0.7 },
|
|
230
|
+
menu: { backgroundColor: c.superficie, borderRadius: 12 },
|
|
231
|
+
insignia: {
|
|
232
|
+
position: "absolute",
|
|
233
|
+
// Dentro del círculo y no colgada de su esquina: en Android lo que se sale del padre se
|
|
234
|
+
// recorta, y la cifra quedaba a medias.
|
|
235
|
+
top: 0,
|
|
236
|
+
right: 0,
|
|
237
|
+
paddingHorizontal: 4,
|
|
238
|
+
alignItems: "center",
|
|
239
|
+
justifyContent: "center",
|
|
240
|
+
backgroundColor: c.primary,
|
|
241
|
+
borderWidth: 2,
|
|
242
|
+
borderColor: c.superficie,
|
|
243
|
+
},
|
|
244
|
+
insigniaTexto: { ...renglon(tipografia.etiqueta), fontWeight: "800", color: c.sobrePrimario },
|
|
245
|
+
menuTexto: { fontSize: 17, fontWeight: "600" },
|
|
246
|
+
rojo: { color: c.primary },
|
|
247
|
+
}));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Pressable, StyleSheet } from "react-native";
|
|
2
|
+
import { Avatar } from "react-native-paper";
|
|
3
|
+
import { tipografia } from "../tema";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* La inicial de cada parte, hasta dos: `inicialesDe(nombre, apellidos)` da «JP»; con sólo el nombre
|
|
7
|
+
* de la cuenta, su primera letra; sin nada, una inicial neutra.
|
|
8
|
+
*/
|
|
9
|
+
export const inicialesDe = (...partes: (string | null | undefined)[]): string =>
|
|
10
|
+
(
|
|
11
|
+
partes
|
|
12
|
+
.map((p) => (p ?? "").trim().charAt(0))
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.slice(0, 2)
|
|
15
|
+
.join("") || "?"
|
|
16
|
+
).toUpperCase();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* El avatar de quien entró, en el header de las pestañas: el acceso al perfil (y a cerrar sesión).
|
|
20
|
+
* Quien lo pone decide las iniciales y a dónde lleva.
|
|
21
|
+
*/
|
|
22
|
+
const BotonDeAvatar = ({
|
|
23
|
+
iniciales,
|
|
24
|
+
onPress,
|
|
25
|
+
etiqueta = "Perfil",
|
|
26
|
+
}: {
|
|
27
|
+
iniciales: string;
|
|
28
|
+
onPress: () => void;
|
|
29
|
+
/** Lo que lee el lector de pantalla. */
|
|
30
|
+
etiqueta?: string;
|
|
31
|
+
}) => (
|
|
32
|
+
<Pressable
|
|
33
|
+
onPress={onPress}
|
|
34
|
+
hitSlop={8}
|
|
35
|
+
accessibilityRole="button"
|
|
36
|
+
accessibilityLabel={etiqueta}
|
|
37
|
+
style={({ pressed }) => [styles.btn, pressed && { opacity: 0.6 }]}
|
|
38
|
+
>
|
|
39
|
+
<Avatar.Text
|
|
40
|
+
size={36}
|
|
41
|
+
label={iniciales.slice(0, 2).toUpperCase()}
|
|
42
|
+
// Blanca sobre el rojo en los dos modos: el tema oscuro de Paper la ponía oscura.
|
|
43
|
+
color="#ffffff"
|
|
44
|
+
labelStyle={styles.initial}
|
|
45
|
+
/>
|
|
46
|
+
</Pressable>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const styles = StyleSheet.create({
|
|
50
|
+
btn: {
|
|
51
|
+
width: 40,
|
|
52
|
+
height: 40,
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
justifyContent: "center",
|
|
55
|
+
},
|
|
56
|
+
initial: { fontSize: tipografia.etiqueta, fontWeight: "700", lineHeight: 20 },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default BotonDeAvatar;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
2
|
+
import IconoDeRenglon from "../componentes/IconoDeRenglon";
|
|
3
|
+
import { Text } from "../texto";
|
|
4
|
+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { ActivityIndicator, Pressable, View } from "react-native";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
children: string;
|
|
10
|
+
icono?: React.ComponentProps<typeof MaterialCommunityIcons>["name"];
|
|
11
|
+
onPress: () => void;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
cargando?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* `relleno` para la acción principal; `contorno` para las de apoyo; `destructivo` para la que
|
|
16
|
+
* borra o deshace algo (Deshacer, Descartar, Cerrar sesión), en rojo oscuro; `listo` para algo
|
|
17
|
+
* ya cumplido que todavía se puede abrir (la evidencia completa), en verde.
|
|
18
|
+
*/
|
|
19
|
+
modo?: "relleno" | "contorno" | "destructivo" | "listo";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* El botón de una acción del flujo (Iniciar, Completar, Reintentar): ancho completo, alto
|
|
24
|
+
* generoso y letra de subtítulo, para atinarle de pie y con la letra del teléfono al 180%.
|
|
25
|
+
*
|
|
26
|
+
* Es propio y no el `Button` de Paper porque ése corta su texto a un renglón: con la letra grande
|
|
27
|
+
* «Iniciar asignación desde donde estás» quedaba en «Iniciar asignac…». Aquí el texto parte en los
|
|
28
|
+
* renglones que haga falta y el botón crece con él.
|
|
29
|
+
*/
|
|
30
|
+
const BotonGrande = ({ children, icono, onPress, disabled, cargando, modo = "relleno" }: Props) => {
|
|
31
|
+
const c = usePaleta();
|
|
32
|
+
const styles = useStyles();
|
|
33
|
+
const inactivo = disabled || cargando;
|
|
34
|
+
// El destructivo es un relleno de otro color: misma tinta blanca, mismo apagado.
|
|
35
|
+
const relleno = modo !== "contorno";
|
|
36
|
+
const tinta = relleno ? c.sobrePrimario : c.primary;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Pressable
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
disabled={inactivo}
|
|
42
|
+
accessibilityRole="button"
|
|
43
|
+
accessibilityState={{ disabled: !!inactivo, busy: !!cargando }}
|
|
44
|
+
style={({ pressed }) => [
|
|
45
|
+
styles.boton,
|
|
46
|
+
relleno
|
|
47
|
+
? modo === "destructivo"
|
|
48
|
+
? styles.destructivo
|
|
49
|
+
: modo === "listo"
|
|
50
|
+
? styles.listo
|
|
51
|
+
: styles.relleno
|
|
52
|
+
: styles.contorno,
|
|
53
|
+
inactivo && (relleno ? styles.rellenoInactivo : styles.contornoInactivo),
|
|
54
|
+
pressed && { opacity: 0.85 },
|
|
55
|
+
]}
|
|
56
|
+
>
|
|
57
|
+
<View style={styles.fila}>
|
|
58
|
+
{cargando ? (
|
|
59
|
+
<ActivityIndicator color={tinta} />
|
|
60
|
+
) : icono ? (
|
|
61
|
+
<IconoDeRenglon
|
|
62
|
+
name={icono}
|
|
63
|
+
fontSize={tipografia.subtitulo}
|
|
64
|
+
size={26}
|
|
65
|
+
color={inactivo ? c.secondaryLight : tinta}
|
|
66
|
+
/>
|
|
67
|
+
) : null}
|
|
68
|
+
<Text style={[styles.texto, { color: inactivo ? c.secondaryLight : tinta }]}>
|
|
69
|
+
{children}
|
|
70
|
+
</Text>
|
|
71
|
+
</View>
|
|
72
|
+
</Pressable>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const useStyles = crearEstilos((c) => ({
|
|
77
|
+
boton: {
|
|
78
|
+
minHeight: 64,
|
|
79
|
+
borderRadius: 14,
|
|
80
|
+
paddingVertical: 14,
|
|
81
|
+
paddingHorizontal: 18,
|
|
82
|
+
justifyContent: "center",
|
|
83
|
+
},
|
|
84
|
+
relleno: { backgroundColor: c.primary },
|
|
85
|
+
destructivo: { backgroundColor: c.primaryDark },
|
|
86
|
+
// Un relleno con letra blanca: el verde oscuro se lee igual en los dos modos.
|
|
87
|
+
listo: { backgroundColor: "#2e7d32" },
|
|
88
|
+
rellenoInactivo: { backgroundColor: c.secondaryLighter },
|
|
89
|
+
contorno: { borderWidth: 2, borderColor: c.primary, backgroundColor: c.superficie },
|
|
90
|
+
contornoInactivo: { borderColor: c.secondaryLighter },
|
|
91
|
+
fila: { flexDirection: "row", alignItems: "flex-start", justifyContent: "center", gap: 10 },
|
|
92
|
+
texto: {
|
|
93
|
+
flexShrink: 1,
|
|
94
|
+
...renglon(tipografia.subtitulo),
|
|
95
|
+
fontWeight: "800",
|
|
96
|
+
textAlign: "center",
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
export default BotonGrande;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import AccionesDelEncabezado, {
|
|
2
|
+
type AccionDelEncabezado,
|
|
3
|
+
} from "../componentes/AccionesDelEncabezado";
|
|
4
|
+
import { Text } from "../texto";
|
|
5
|
+
import { crearEstilos, tipografia, usePaleta } from "../tema";
|
|
6
|
+
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
|
7
|
+
import { router } from "expo-router";
|
|
8
|
+
import type React from "react";
|
|
9
|
+
import { TouchableOpacity, View } from "react-native";
|
|
10
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* La cabecera de una pantalla empujada encima de las pestañas: atrás, el rótulo, el título y, si
|
|
14
|
+
* hay, sus acciones a la derecha (`AccionesDelEncabezado`). La usan las fichas, las
|
|
15
|
+
* pantallas de edición y el perfil.
|
|
16
|
+
*
|
|
17
|
+
* `estado` va debajo del título —el estado de la ficha, normalmente un `Chip`— y no a su lado: con
|
|
18
|
+
* acciones a la derecha, un folio largo y su chip en el mismo renglón se peleaban el ancho.
|
|
19
|
+
*/
|
|
20
|
+
const CabeceraEmpujada = ({
|
|
21
|
+
eyebrow,
|
|
22
|
+
titulo,
|
|
23
|
+
acciones,
|
|
24
|
+
estado,
|
|
25
|
+
}: {
|
|
26
|
+
eyebrow: string;
|
|
27
|
+
titulo: string;
|
|
28
|
+
acciones?: AccionDelEncabezado[];
|
|
29
|
+
estado?: React.ReactNode;
|
|
30
|
+
}) => {
|
|
31
|
+
const c = usePaleta();
|
|
32
|
+
const styles = useStyles();
|
|
33
|
+
const insets = useSafeAreaInsets();
|
|
34
|
+
return (
|
|
35
|
+
<View style={[styles.hdr, { paddingTop: insets.top + 10 }]}>
|
|
36
|
+
<View style={styles.hdrRow}>
|
|
37
|
+
<TouchableOpacity onPress={() => router.back()} accessibilityLabel="Atrás">
|
|
38
|
+
<MaterialIcons name="arrow-back" size={45} color={c.primary} />
|
|
39
|
+
</TouchableOpacity>
|
|
40
|
+
{/* `flexShrink` y no `flex`: con acciones, el título les deja su hueco. */}
|
|
41
|
+
<View style={acciones?.length ? styles.tituloConAcciones : styles.titulo}>
|
|
42
|
+
<Text style={styles.hdrEyebrow}>{eyebrow}</Text>
|
|
43
|
+
<Text style={styles.hdrTitle}>{titulo}</Text>
|
|
44
|
+
{estado ? <View style={styles.estado}>{estado}</View> : null}
|
|
45
|
+
</View>
|
|
46
|
+
{acciones?.length ? <AccionesDelEncabezado acciones={acciones} /> : null}
|
|
47
|
+
</View>
|
|
48
|
+
</View>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const useStyles = crearEstilos((c) => ({
|
|
53
|
+
hdr: { paddingHorizontal: 18, paddingBottom: 13, backgroundColor: c.superficie },
|
|
54
|
+
hdrRow: { flexDirection: "row", alignItems: "flex-end", gap: 8 },
|
|
55
|
+
titulo: { flex: 1, minWidth: 0 },
|
|
56
|
+
tituloConAcciones: { flexShrink: 1, minWidth: 0 },
|
|
57
|
+
hdrEyebrow: {
|
|
58
|
+
fontSize: tipografia.etiqueta,
|
|
59
|
+
color: c.secondaryLight,
|
|
60
|
+
textTransform: "uppercase",
|
|
61
|
+
letterSpacing: 1.6,
|
|
62
|
+
marginBottom: 4,
|
|
63
|
+
fontWeight: "600",
|
|
64
|
+
},
|
|
65
|
+
hdrTitle: { fontSize: tipografia.titulo, fontWeight: "900", color: c.primary },
|
|
66
|
+
estado: { marginTop: 6, flexDirection: "row", flexWrap: "wrap", gap: 6 },
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
export default CabeceraEmpujada;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Text } from "../texto";
|
|
2
|
+
import { crearEstilos, type Paleta, renglon, tipografia, usePaleta } from "../tema";
|
|
3
|
+
import { View, type StyleProp, type ViewStyle } from "react-native";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* El tono de un chip, por lo que quiere decir y no por su color: así un estado se ve igual en
|
|
7
|
+
* claro y en oscuro, y dos pantallas no le ponen dos colores a lo mismo.
|
|
8
|
+
*/
|
|
9
|
+
export type TonoDeChip = "exito" | "aviso" | "info" | "error" | "morado" | "neutro" | "primario";
|
|
10
|
+
|
|
11
|
+
const colores = (c: Paleta, tono: TonoDeChip) => {
|
|
12
|
+
switch (tono) {
|
|
13
|
+
case "exito":
|
|
14
|
+
return c.exito;
|
|
15
|
+
case "aviso":
|
|
16
|
+
return { fondo: c.aviso.fondo, tinta: c.aviso.tinta };
|
|
17
|
+
case "info":
|
|
18
|
+
return c.info;
|
|
19
|
+
case "error":
|
|
20
|
+
return c.error;
|
|
21
|
+
case "morado":
|
|
22
|
+
return c.morado;
|
|
23
|
+
case "primario":
|
|
24
|
+
return { fondo: c.primary, tinta: c.sobrePrimario };
|
|
25
|
+
default:
|
|
26
|
+
return { fondo: c.superficieTenue, tinta: c.secondary };
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Un estado en una pastilla: «En progreso», «Por vencer», «En viaje». */
|
|
31
|
+
const Chip = ({
|
|
32
|
+
texto,
|
|
33
|
+
tono = "neutro",
|
|
34
|
+
style,
|
|
35
|
+
}: {
|
|
36
|
+
texto: string;
|
|
37
|
+
tono?: TonoDeChip;
|
|
38
|
+
style?: StyleProp<ViewStyle>;
|
|
39
|
+
}) => {
|
|
40
|
+
const c = usePaleta();
|
|
41
|
+
const styles = useStyles();
|
|
42
|
+
const { fondo, tinta } = colores(c, tono);
|
|
43
|
+
return (
|
|
44
|
+
<View style={[styles.chip, { backgroundColor: fondo }, style]}>
|
|
45
|
+
<Text style={[styles.texto, { color: tinta }]}>{texto}</Text>
|
|
46
|
+
</View>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const useStyles = crearEstilos(() => ({
|
|
51
|
+
chip: {
|
|
52
|
+
alignSelf: "flex-start",
|
|
53
|
+
paddingHorizontal: 9,
|
|
54
|
+
paddingVertical: 3,
|
|
55
|
+
borderRadius: 999,
|
|
56
|
+
},
|
|
57
|
+
texto: { ...renglon(tipografia.etiqueta), fontWeight: "700" },
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
export default Chip;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useWindowDimensions, View } from "react-native";
|
|
3
|
+
import { KeyboardAvoidingView } from "react-native-keyboard-controller";
|
|
4
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
|
+
import { crearEstilos } from "../tema";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
paddingBottom?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Cuando el hijo ya maneja el teclado por su cuenta (p. ej. un `KeyboardAwareScrollView`),
|
|
12
|
+
* `false` quita el `KeyboardAvoidingView` para no encimar dos manejadores del teclado.
|
|
13
|
+
*/
|
|
14
|
+
avoidKeyboard?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const BASE_PADDING = 20;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* El cuerpo de una pantalla debajo de su header: el fondo, el margen a los lados (el doble en una
|
|
21
|
+
* tableta, más lo que pida la muesca) y, por omisión, que esquive el teclado.
|
|
22
|
+
*/
|
|
23
|
+
const ContenidoDePantalla = ({ paddingBottom, avoidKeyboard = true, children }: Props) => {
|
|
24
|
+
const styles = useStyles();
|
|
25
|
+
const insets = useSafeAreaInsets();
|
|
26
|
+
const { width } = useWindowDimensions();
|
|
27
|
+
const horizontalPadding = width > 700 ? BASE_PADDING * 2 : BASE_PADDING;
|
|
28
|
+
const containerStyle = [
|
|
29
|
+
styles.container,
|
|
30
|
+
{
|
|
31
|
+
paddingTop: 20,
|
|
32
|
+
paddingLeft: horizontalPadding + insets.left,
|
|
33
|
+
paddingRight: horizontalPadding + insets.right,
|
|
34
|
+
paddingBottom: paddingBottom ?? 0,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
if (!avoidKeyboard) {
|
|
38
|
+
return <View style={containerStyle}>{children}</View>;
|
|
39
|
+
}
|
|
40
|
+
return (
|
|
41
|
+
// `behavior="padding"` funciona en ambas plataformas con edge-to-edge
|
|
42
|
+
// gracias a react-native-keyboard-controller (RN lo ignora en Android).
|
|
43
|
+
<KeyboardAvoidingView behavior="padding" style={containerStyle}>
|
|
44
|
+
{children}
|
|
45
|
+
</KeyboardAvoidingView>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const useStyles = crearEstilos((c) => ({
|
|
50
|
+
container: { flex: 1, backgroundColor: c.fondo },
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
export default ContenidoDePantalla;
|
|
@@ -0,0 +1,45 @@
|
|
|
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 { View } from "react-native";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
icono: React.ComponentProps<typeof MaterialCommunityIcons>["name"];
|
|
9
|
+
titulo: string;
|
|
10
|
+
texto?: string;
|
|
11
|
+
/** Una acción («Reintentar») que va debajo del texto, centrada con él. */
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lo que se pinta cuando una lista no tiene nada que enseñar. Ocupa todo el alto y centra lo suyo,
|
|
17
|
+
* así que una acción va como hijo: puesta después, como hermano, quedaba hasta abajo de la
|
|
18
|
+
* pantalla, debajo de la barra de pestañas.
|
|
19
|
+
*/
|
|
20
|
+
const EstadoVacio = ({ icono, titulo, texto, children }: Props) => {
|
|
21
|
+
const c = usePaleta();
|
|
22
|
+
const styles = useStyles();
|
|
23
|
+
return (
|
|
24
|
+
<View style={styles.root}>
|
|
25
|
+
<MaterialCommunityIcons name={icono} size={56} color={c.secondaryLight} />
|
|
26
|
+
<Text style={styles.titulo}>{titulo}</Text>
|
|
27
|
+
{texto ? <Text style={styles.texto}>{texto}</Text> : null}
|
|
28
|
+
{children ? <View style={styles.accion}>{children}</View> : null}
|
|
29
|
+
</View>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const useStyles = crearEstilos((c) => ({
|
|
34
|
+
root: { flex: 1, alignItems: "center", justifyContent: "center", gap: 10, padding: 24 },
|
|
35
|
+
titulo: {
|
|
36
|
+
...renglon(tipografia.subtitulo),
|
|
37
|
+
fontWeight: "700",
|
|
38
|
+
color: c.secondaryDark,
|
|
39
|
+
textAlign: "center",
|
|
40
|
+
},
|
|
41
|
+
texto: { fontSize: tipografia.cuerpo, color: c.secondary, textAlign: "center" },
|
|
42
|
+
accion: { alignSelf: "stretch", marginTop: 14 },
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
export default EstadoVacio;
|