@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.
Files changed (297) hide show
  1. package/README.md +146 -2
  2. package/assets/fonts/Inter_400Regular.ttf +0 -0
  3. package/assets/fonts/Inter_500Medium.ttf +0 -0
  4. package/assets/fonts/Inter_600SemiBold.ttf +0 -0
  5. package/assets/fonts/Inter_700Bold.ttf +0 -0
  6. package/assets/fonts/Inter_800ExtraBold.ttf +0 -0
  7. package/assets/fonts/Inter_900Black.ttf +0 -0
  8. package/assets/fonts/OFL.txt +93 -0
  9. package/lib/module/componentes/AccionesDelEncabezado.js +212 -0
  10. package/lib/module/componentes/AccionesDelEncabezado.js.map +1 -0
  11. package/lib/module/componentes/BotonDeAvatar.js +55 -0
  12. package/lib/module/componentes/BotonDeAvatar.js.map +1 -0
  13. package/lib/module/componentes/BotonGrande.js +105 -0
  14. package/lib/module/componentes/BotonGrande.js.map +1 -0
  15. package/lib/module/componentes/CabeceraEmpujada.js +101 -0
  16. package/lib/module/componentes/CabeceraEmpujada.js.map +1 -0
  17. package/lib/module/componentes/Chip.js +77 -0
  18. package/lib/module/componentes/Chip.js.map +1 -0
  19. package/lib/module/componentes/ContenidoDePantalla.js +56 -0
  20. package/lib/module/componentes/ContenidoDePantalla.js.map +1 -0
  21. package/lib/module/componentes/EstadoVacio.js +65 -0
  22. package/lib/module/componentes/EstadoVacio.js.map +1 -0
  23. package/lib/module/componentes/Foto.js +106 -0
  24. package/lib/module/componentes/Foto.js.map +1 -0
  25. package/lib/module/componentes/IconoDeRenglon.js +44 -0
  26. package/lib/module/componentes/IconoDeRenglon.js.map +1 -0
  27. package/lib/module/componentes/Logo.js +44 -0
  28. package/lib/module/componentes/Logo.js.map +1 -0
  29. package/lib/module/componentes/Nota.js +51 -0
  30. package/lib/module/componentes/Nota.js.map +1 -0
  31. package/lib/module/componentes/OpcionesExcluyentes.js +69 -0
  32. package/lib/module/componentes/OpcionesExcluyentes.js.map +1 -0
  33. package/lib/module/componentes/PantallaDeError.js +84 -0
  34. package/lib/module/componentes/PantallaDeError.js.map +1 -0
  35. package/lib/module/componentes/Pestanas.js +87 -0
  36. package/lib/module/componentes/Pestanas.js.map +1 -0
  37. package/lib/module/componentes/Rotulo.js +28 -0
  38. package/lib/module/componentes/Rotulo.js.map +1 -0
  39. package/lib/module/componentes/RotuloDeSeccion.js +87 -0
  40. package/lib/module/componentes/RotuloDeSeccion.js.map +1 -0
  41. package/lib/module/componentes/Tarjeta.js +47 -0
  42. package/lib/module/componentes/Tarjeta.js.map +1 -0
  43. package/lib/module/componentes/TarjetaDeDatos.js +79 -0
  44. package/lib/module/componentes/TarjetaDeDatos.js.map +1 -0
  45. package/lib/module/componentes/TiraDeFotos.js +184 -0
  46. package/lib/module/componentes/TiraDeFotos.js.map +1 -0
  47. package/lib/module/componentes/VisorDeFoto.js +171 -0
  48. package/lib/module/componentes/VisorDeFoto.js.map +1 -0
  49. package/lib/module/componentes/logos/bola-blanca.png +0 -0
  50. package/lib/module/componentes/logos/bola.png +0 -0
  51. package/lib/module/componentes/logos/logo-blanco.png +0 -0
  52. package/lib/module/componentes/logos/logo.png +0 -0
  53. package/lib/module/dialogos/HostDeDialogos.js +170 -0
  54. package/lib/module/dialogos/HostDeDialogos.js.map +1 -0
  55. package/lib/module/dialogos/dialogos.js +112 -0
  56. package/lib/module/dialogos/dialogos.js.map +1 -0
  57. package/lib/module/formularios/CampoDeSeleccion.js +47 -0
  58. package/lib/module/formularios/CampoDeSeleccion.js.map +1 -0
  59. package/lib/module/formularios/SelectorDeOpcion.js +406 -0
  60. package/lib/module/formularios/SelectorDeOpcion.js.map +1 -0
  61. package/lib/module/formularios/TextoDeAccesoControlado.js +73 -0
  62. package/lib/module/formularios/TextoDeAccesoControlado.js.map +1 -0
  63. package/lib/module/formularios/campos.js +493 -0
  64. package/lib/module/formularios/campos.js.map +1 -0
  65. package/lib/module/formularios/controlados.js +197 -0
  66. package/lib/module/formularios/controlados.js.map +1 -0
  67. package/lib/module/formularios/errores.js +91 -0
  68. package/lib/module/formularios/errores.js.map +1 -0
  69. package/lib/module/formularios/guardado.js +78 -0
  70. package/lib/module/formularios/guardado.js.map +1 -0
  71. package/lib/module/formularios/useBusqueda.js +22 -0
  72. package/lib/module/formularios/useBusqueda.js.map +1 -0
  73. package/lib/module/fotos/elegir.js +178 -0
  74. package/lib/module/fotos/elegir.js.map +1 -0
  75. package/lib/module/fotos/index.js +8 -0
  76. package/lib/module/fotos/index.js.map +1 -0
  77. package/lib/module/fotos/reducir.js +93 -0
  78. package/lib/module/fotos/reducir.js.map +1 -0
  79. package/lib/module/hoja/HojaInferior.js +108 -0
  80. package/lib/module/hoja/HojaInferior.js.map +1 -0
  81. package/lib/module/hoja/index.js +7 -0
  82. package/lib/module/hoja/index.js.map +1 -0
  83. package/lib/module/hoja/useMasDeLaFicha.js +48 -0
  84. package/lib/module/hoja/useMasDeLaFicha.js.map +1 -0
  85. package/lib/module/index.js +66 -0
  86. package/lib/module/index.js.map +1 -0
  87. package/lib/module/listas/HojaDeFiltros.js +260 -0
  88. package/lib/module/listas/HojaDeFiltros.js.map +1 -0
  89. package/lib/module/listas/ListaPaginada.js +103 -0
  90. package/lib/module/listas/ListaPaginada.js.map +1 -0
  91. package/lib/module/listas/TarjetaDeslizable.js +118 -0
  92. package/lib/module/listas/TarjetaDeslizable.js.map +1 -0
  93. package/lib/module/listas/paginado.js +11 -0
  94. package/lib/module/listas/paginado.js.map +1 -0
  95. package/lib/module/listas/piezas.js +603 -0
  96. package/lib/module/listas/piezas.js.map +1 -0
  97. package/lib/module/navegacion/BarraDePestanas.js +162 -0
  98. package/lib/module/navegacion/BarraDePestanas.js.map +1 -0
  99. package/lib/module/navegacion/BotonDeBarra.js +58 -0
  100. package/lib/module/navegacion/BotonDeBarra.js.map +1 -0
  101. package/lib/module/navegacion/encabezado.js +103 -0
  102. package/lib/module/navegacion/encabezado.js.map +1 -0
  103. package/lib/module/pantallas/PantallaDeAcceso.js +195 -0
  104. package/lib/module/pantallas/PantallaDeAcceso.js.map +1 -0
  105. package/lib/module/pantallas/PantallaDeEdicion.js +121 -0
  106. package/lib/module/pantallas/PantallaDeEdicion.js.map +1 -0
  107. package/lib/module/pantallas/PantallaDeFicha.js +94 -0
  108. package/lib/module/pantallas/PantallaDeFicha.js.map +1 -0
  109. package/lib/module/pantallas/PantallaDePerfil.js +137 -0
  110. package/lib/module/pantallas/PantallaDePerfil.js.map +1 -0
  111. package/lib/module/tema/ProveedorDeTema.js +122 -0
  112. package/lib/module/tema/ProveedorDeTema.js.map +1 -0
  113. package/lib/module/tema/estilos.js +54 -0
  114. package/lib/module/tema/estilos.js.map +1 -0
  115. package/lib/module/tema/fuentes.js +28 -0
  116. package/lib/module/tema/fuentes.js.map +1 -0
  117. package/lib/module/tema/index.js +8 -0
  118. package/lib/module/tema/index.js.map +1 -0
  119. package/lib/module/tema/paleta.js +107 -0
  120. package/lib/module/tema/paleta.js.map +1 -0
  121. package/lib/module/tema/tipografia.js +32 -0
  122. package/lib/module/tema/tipografia.js.map +1 -0
  123. package/lib/module/texto.js +48 -0
  124. package/lib/module/texto.js.map +1 -0
  125. package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts +34 -0
  126. package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts.map +1 -0
  127. package/lib/typescript/src/componentes/BotonDeAvatar.d.ts +17 -0
  128. package/lib/typescript/src/componentes/BotonDeAvatar.d.ts.map +1 -0
  129. package/lib/typescript/src/componentes/BotonGrande.d.ts +26 -0
  130. package/lib/typescript/src/componentes/BotonGrande.d.ts.map +1 -0
  131. package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts +18 -0
  132. package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts.map +1 -0
  133. package/lib/typescript/src/componentes/Chip.d.ts +14 -0
  134. package/lib/typescript/src/componentes/Chip.d.ts.map +1 -0
  135. package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts +17 -0
  136. package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts.map +1 -0
  137. package/lib/typescript/src/componentes/EstadoVacio.d.ts +17 -0
  138. package/lib/typescript/src/componentes/EstadoVacio.d.ts.map +1 -0
  139. package/lib/typescript/src/componentes/Foto.d.ts +41 -0
  140. package/lib/typescript/src/componentes/Foto.d.ts.map +1 -0
  141. package/lib/typescript/src/componentes/IconoDeRenglon.d.ts +25 -0
  142. package/lib/typescript/src/componentes/IconoDeRenglon.d.ts.map +1 -0
  143. package/lib/typescript/src/componentes/Logo.d.ts +13 -0
  144. package/lib/typescript/src/componentes/Logo.d.ts.map +1 -0
  145. package/lib/typescript/src/componentes/Nota.d.ts +7 -0
  146. package/lib/typescript/src/componentes/Nota.d.ts.map +1 -0
  147. package/lib/typescript/src/componentes/OpcionesExcluyentes.d.ts +14 -0
  148. package/lib/typescript/src/componentes/OpcionesExcluyentes.d.ts.map +1 -0
  149. package/lib/typescript/src/componentes/PantallaDeError.d.ts +23 -0
  150. package/lib/typescript/src/componentes/PantallaDeError.d.ts.map +1 -0
  151. package/lib/typescript/src/componentes/Pestanas.d.ts +28 -0
  152. package/lib/typescript/src/componentes/Pestanas.d.ts.map +1 -0
  153. package/lib/typescript/src/componentes/Rotulo.d.ts +6 -0
  154. package/lib/typescript/src/componentes/Rotulo.d.ts.map +1 -0
  155. package/lib/typescript/src/componentes/RotuloDeSeccion.d.ts +20 -0
  156. package/lib/typescript/src/componentes/RotuloDeSeccion.d.ts.map +1 -0
  157. package/lib/typescript/src/componentes/Tarjeta.d.ts +15 -0
  158. package/lib/typescript/src/componentes/Tarjeta.d.ts.map +1 -0
  159. package/lib/typescript/src/componentes/TarjetaDeDatos.d.ts +13 -0
  160. package/lib/typescript/src/componentes/TarjetaDeDatos.d.ts.map +1 -0
  161. package/lib/typescript/src/componentes/TiraDeFotos.d.ts +36 -0
  162. package/lib/typescript/src/componentes/TiraDeFotos.d.ts.map +1 -0
  163. package/lib/typescript/src/componentes/VisorDeFoto.d.ts +23 -0
  164. package/lib/typescript/src/componentes/VisorDeFoto.d.ts.map +1 -0
  165. package/lib/typescript/src/dialogos/HostDeDialogos.d.ts +14 -0
  166. package/lib/typescript/src/dialogos/HostDeDialogos.d.ts.map +1 -0
  167. package/lib/typescript/src/dialogos/dialogos.d.ts +65 -0
  168. package/lib/typescript/src/dialogos/dialogos.d.ts.map +1 -0
  169. package/lib/typescript/src/formularios/CampoDeSeleccion.d.ts +22 -0
  170. package/lib/typescript/src/formularios/CampoDeSeleccion.d.ts.map +1 -0
  171. package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts +59 -0
  172. package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts.map +1 -0
  173. package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts +18 -0
  174. package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts.map +1 -0
  175. package/lib/typescript/src/formularios/campos.d.ts +107 -0
  176. package/lib/typescript/src/formularios/campos.d.ts.map +1 -0
  177. package/lib/typescript/src/formularios/controlados.d.ts +29 -0
  178. package/lib/typescript/src/formularios/controlados.d.ts.map +1 -0
  179. package/lib/typescript/src/formularios/errores.d.ts +36 -0
  180. package/lib/typescript/src/formularios/errores.d.ts.map +1 -0
  181. package/lib/typescript/src/formularios/guardado.d.ts +36 -0
  182. package/lib/typescript/src/formularios/guardado.d.ts.map +1 -0
  183. package/lib/typescript/src/formularios/useBusqueda.d.ts +7 -0
  184. package/lib/typescript/src/formularios/useBusqueda.d.ts.map +1 -0
  185. package/lib/typescript/src/fotos/elegir.d.ts +58 -0
  186. package/lib/typescript/src/fotos/elegir.d.ts.map +1 -0
  187. package/lib/typescript/src/fotos/index.d.ts +3 -0
  188. package/lib/typescript/src/fotos/index.d.ts.map +1 -0
  189. package/lib/typescript/src/fotos/reducir.d.ts +32 -0
  190. package/lib/typescript/src/fotos/reducir.d.ts.map +1 -0
  191. package/lib/typescript/src/hoja/HojaInferior.d.ts +21 -0
  192. package/lib/typescript/src/hoja/HojaInferior.d.ts.map +1 -0
  193. package/lib/typescript/src/hoja/index.d.ts +3 -0
  194. package/lib/typescript/src/hoja/index.d.ts.map +1 -0
  195. package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts +27 -0
  196. package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts.map +1 -0
  197. package/lib/typescript/src/index.d.ts +45 -0
  198. package/lib/typescript/src/index.d.ts.map +1 -0
  199. package/lib/typescript/src/listas/HojaDeFiltros.d.ts +60 -0
  200. package/lib/typescript/src/listas/HojaDeFiltros.d.ts.map +1 -0
  201. package/lib/typescript/src/listas/ListaPaginada.d.ts +39 -0
  202. package/lib/typescript/src/listas/ListaPaginada.d.ts.map +1 -0
  203. package/lib/typescript/src/listas/TarjetaDeslizable.d.ts +33 -0
  204. package/lib/typescript/src/listas/TarjetaDeslizable.d.ts.map +1 -0
  205. package/lib/typescript/src/listas/paginado.d.ts +16 -0
  206. package/lib/typescript/src/listas/paginado.d.ts.map +1 -0
  207. package/lib/typescript/src/listas/piezas.d.ts +135 -0
  208. package/lib/typescript/src/listas/piezas.d.ts.map +1 -0
  209. package/lib/typescript/src/navegacion/BarraDePestanas.d.ts +57 -0
  210. package/lib/typescript/src/navegacion/BarraDePestanas.d.ts.map +1 -0
  211. package/lib/typescript/src/navegacion/BotonDeBarra.d.ts +15 -0
  212. package/lib/typescript/src/navegacion/BotonDeBarra.d.ts.map +1 -0
  213. package/lib/typescript/src/navegacion/encabezado.d.ts +44 -0
  214. package/lib/typescript/src/navegacion/encabezado.d.ts.map +1 -0
  215. package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts +32 -0
  216. package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts.map +1 -0
  217. package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts +38 -0
  218. package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts.map +1 -0
  219. package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts +35 -0
  220. package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts.map +1 -0
  221. package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts +19 -0
  222. package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts.map +1 -0
  223. package/lib/typescript/src/tema/ProveedorDeTema.d.ts +32 -0
  224. package/lib/typescript/src/tema/ProveedorDeTema.d.ts.map +1 -0
  225. package/lib/typescript/src/tema/estilos.d.ts +31 -0
  226. package/lib/typescript/src/tema/estilos.d.ts.map +1 -0
  227. package/lib/typescript/src/tema/fuentes.d.ts +20 -0
  228. package/lib/typescript/src/tema/fuentes.d.ts.map +1 -0
  229. package/lib/typescript/src/tema/index.d.ts +6 -0
  230. package/lib/typescript/src/tema/index.d.ts.map +1 -0
  231. package/lib/typescript/src/tema/paleta.d.ts +86 -0
  232. package/lib/typescript/src/tema/paleta.d.ts.map +1 -0
  233. package/lib/typescript/src/tema/tipografia.d.ts +29 -0
  234. package/lib/typescript/src/tema/tipografia.d.ts.map +1 -0
  235. package/lib/typescript/src/texto.d.ts +16 -0
  236. package/lib/typescript/src/texto.d.ts.map +1 -0
  237. package/package.json +135 -5
  238. package/src/componentes/AccionesDelEncabezado.tsx +247 -0
  239. package/src/componentes/BotonDeAvatar.tsx +59 -0
  240. package/src/componentes/BotonGrande.tsx +100 -0
  241. package/src/componentes/CabeceraEmpujada.tsx +69 -0
  242. package/src/componentes/Chip.tsx +60 -0
  243. package/src/componentes/ContenidoDePantalla.tsx +53 -0
  244. package/src/componentes/EstadoVacio.tsx +45 -0
  245. package/src/componentes/Foto.tsx +92 -0
  246. package/src/componentes/IconoDeRenglon.tsx +42 -0
  247. package/src/componentes/Logo.tsx +34 -0
  248. package/src/componentes/Nota.tsx +37 -0
  249. package/src/componentes/OpcionesExcluyentes.tsx +62 -0
  250. package/src/componentes/PantallaDeError.tsx +76 -0
  251. package/src/componentes/Pestanas.tsx +86 -0
  252. package/src/componentes/Rotulo.tsx +21 -0
  253. package/src/componentes/RotuloDeSeccion.tsx +71 -0
  254. package/src/componentes/Tarjeta.tsx +44 -0
  255. package/src/componentes/TarjetaDeDatos.tsx +62 -0
  256. package/src/componentes/TiraDeFotos.tsx +189 -0
  257. package/src/componentes/VisorDeFoto.tsx +181 -0
  258. package/src/componentes/logos/bola-blanca.png +0 -0
  259. package/src/componentes/logos/bola.png +0 -0
  260. package/src/componentes/logos/logo-blanco.png +0 -0
  261. package/src/componentes/logos/logo.png +0 -0
  262. package/src/dialogos/HostDeDialogos.tsx +172 -0
  263. package/src/dialogos/dialogos.ts +148 -0
  264. package/src/formularios/CampoDeSeleccion.tsx +46 -0
  265. package/src/formularios/SelectorDeOpcion.tsx +407 -0
  266. package/src/formularios/TextoDeAccesoControlado.tsx +72 -0
  267. package/src/formularios/campos.tsx +483 -0
  268. package/src/formularios/controlados.tsx +198 -0
  269. package/src/formularios/errores.ts +105 -0
  270. package/src/formularios/guardado.ts +76 -0
  271. package/src/formularios/useBusqueda.ts +19 -0
  272. package/src/fotos/elegir.ts +194 -0
  273. package/src/fotos/index.ts +15 -0
  274. package/src/fotos/reducir.ts +87 -0
  275. package/src/hoja/HojaInferior.tsx +102 -0
  276. package/src/hoja/index.ts +4 -0
  277. package/src/hoja/useMasDeLaFicha.tsx +63 -0
  278. package/src/index.ts +143 -0
  279. package/src/listas/HojaDeFiltros.tsx +261 -0
  280. package/src/listas/ListaPaginada.tsx +117 -0
  281. package/src/listas/TarjetaDeslizable.tsx +126 -0
  282. package/src/listas/paginado.ts +15 -0
  283. package/src/listas/piezas.tsx +594 -0
  284. package/src/navegacion/BarraDePestanas.tsx +198 -0
  285. package/src/navegacion/BotonDeBarra.tsx +61 -0
  286. package/src/navegacion/encabezado.tsx +88 -0
  287. package/src/pantallas/PantallaDeAcceso.tsx +184 -0
  288. package/src/pantallas/PantallaDeEdicion.tsx +116 -0
  289. package/src/pantallas/PantallaDeFicha.tsx +115 -0
  290. package/src/pantallas/PantallaDePerfil.tsx +126 -0
  291. package/src/tema/ProveedorDeTema.tsx +150 -0
  292. package/src/tema/estilos.ts +51 -0
  293. package/src/tema/fuentes.ts +20 -0
  294. package/src/tema/index.ts +10 -0
  295. package/src/tema/paleta.ts +147 -0
  296. package/src/tema/tipografia.ts +29 -0
  297. package/src/texto.tsx +44 -0
@@ -0,0 +1,46 @@
1
+ import { View } from "react-native";
2
+ import { ErrorDelCampo } from "./campos";
3
+ import SelectorDeOpcion, {
4
+ SelectorDeVarias,
5
+ type Opcion,
6
+ type PropsDeSelectorDeOpcion,
7
+ type PropsDeSelectorDeVarias,
8
+ } from "./SelectorDeOpcion";
9
+
10
+ /**
11
+ * Un campo que se elige de una lista larga —un catálogo del servidor, uno guardado en el
12
+ * teléfono—: `SelectorDeOpcion` con el error del formulario debajo. Es el `ControlledAutocomplete`
13
+ * del portal.
14
+ *
15
+ * Quien lo usa trae las opciones y, si son del servidor, la búsqueda: en una app es su consulta del
16
+ * catálogo (`useBusqueda` para no pedir una vez por letra). Una lista fija se pasa sin `onBuscar`
17
+ * y se filtra sola.
18
+ */
19
+ export default function CampoDeSeleccion<T extends Opcion>({
20
+ error,
21
+ ...props
22
+ }: PropsDeSelectorDeOpcion<T> & { error?: string }) {
23
+ return (
24
+ <View>
25
+ <SelectorDeOpcion<T> {...props} />
26
+ <ErrorDelCampo error={error} />
27
+ </View>
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Lo mismo, pero se eligen varias (los recibos de un concepto, las piezas sobre las que se cobra
33
+ * un porcentaje): `SelectorDeVarias` con el error debajo. El valor es la lista de opciones
34
+ * enteras, igual que el de una es la opción entera.
35
+ */
36
+ export function CampoDeVarias<T extends Opcion>({
37
+ error,
38
+ ...props
39
+ }: PropsDeSelectorDeVarias<T> & { error?: string }) {
40
+ return (
41
+ <View>
42
+ <SelectorDeVarias<T> {...props} />
43
+ <ErrorDelCampo error={error} />
44
+ </View>
45
+ );
46
+ }
@@ -0,0 +1,407 @@
1
+ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
2
+ import React from "react";
3
+ import {
4
+ ActivityIndicator,
5
+ FlatList,
6
+ Modal,
7
+ Pressable,
8
+ TouchableOpacity,
9
+ View,
10
+ } from "react-native";
11
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
12
+ import BotonGrande from "../componentes/BotonGrande";
13
+ import EstadoVacio from "../componentes/EstadoVacio";
14
+ import IconoDeRenglon from "../componentes/IconoDeRenglon";
15
+ import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
16
+ import { Text, TextInput } from "../texto";
17
+
18
+ /** Una opción de una lista: lo que se guarda (`value`) y lo que se lee (`label`). */
19
+ export interface Opcion {
20
+ value: number | string;
21
+ label: string;
22
+ }
23
+
24
+ /** Lo que tienen en común el selector de una y el de varias: de dónde salen las opciones. */
25
+ interface FuenteDeOpciones<T extends Opcion> {
26
+ etiqueta: string;
27
+ opciones: T[];
28
+ /**
29
+ * Si las opciones las trae el servidor, mientras llegan. Una lista fija (las modalidades, los
30
+ * estados) no lo pasa.
31
+ */
32
+ cargando?: boolean;
33
+ /**
34
+ * El texto de la búsqueda, para que quien llama pida al servidor. **Sin `onBuscar` la lista es
35
+ * fija** y se filtra aquí, por lo que se lee: así una lista de cinco opciones no necesita una
36
+ * consulta ni un estado en la pantalla.
37
+ */
38
+ texto?: string;
39
+ onBuscar?: (texto: string) => void;
40
+ /** Lo que va debajo del nombre de cada opción (un viaje: sus unidades). */
41
+ detalle?: (opcion: T) => string | null;
42
+ /** Para agrupar las opciones con un rótulo (los viajes esperados primero). */
43
+ grupo?: (opcion: T) => string;
44
+ vacio?: string;
45
+ disabled?: boolean;
46
+ }
47
+
48
+ export interface PropsDeSelectorDeOpcion<T extends Opcion> extends FuenteDeOpciones<T> {
49
+ valor: T | null;
50
+ onElegir: (opcion: T | null) => void;
51
+ /** Si se puede dejar vacío: agrega «Ninguno» arriba de la lista. */
52
+ opcional?: boolean;
53
+ /** Cómo se dice el vacío de `opcional`: «Todos» en un filtro. */
54
+ textoDeNinguno?: string;
55
+ }
56
+
57
+ export interface PropsDeSelectorDeVarias<T extends Opcion> extends FuenteDeOpciones<T> {
58
+ valor: T[];
59
+ onElegir: (opciones: T[]) => void;
60
+ }
61
+
62
+ /** Para comparar lo escrito con una opción sin que estorben los acentos ni las mayúsculas. */
63
+ const normalizar = (texto: string) =>
64
+ texto.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
65
+
66
+ /**
67
+ * La búsqueda de un selector: la de quien llama si trae `onBuscar`, o una propia que filtra la
68
+ * lista fija aquí mismo.
69
+ */
70
+ function useBuscador<T extends Opcion>({ opciones, texto, onBuscar }: FuenteDeOpciones<T>) {
71
+ const [propio, setPropio] = React.useState("");
72
+ const fija = !onBuscar;
73
+ const actual = fija ? propio : (texto ?? "");
74
+ const visibles = React.useMemo(() => {
75
+ if (!fija || !propio.trim()) return opciones;
76
+ const buscado = normalizar(propio);
77
+ return opciones.filter((opcion) => normalizar(opcion.label).includes(buscado));
78
+ }, [fija, propio, opciones]);
79
+ return {
80
+ texto: actual,
81
+ buscar: fija ? setPropio : onBuscar,
82
+ visibles,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * La lista a pantalla completa de los dos selectores: la cabecera con la flecha, el buscador, y
88
+ * las opciones como tarjetas. Lo que pasa al tocar una —elegirla y cerrar, o marcarla— y lo que
89
+ * va al pie es de cada selector.
90
+ */
91
+ function HojaDeOpciones<T extends Opcion>({
92
+ etiqueta,
93
+ abierto,
94
+ onCerrar,
95
+ texto,
96
+ onBuscar,
97
+ cargando,
98
+ opciones,
99
+ marcada,
100
+ onTocar,
101
+ detalle,
102
+ grupo,
103
+ vacio = "Nada que coincida.",
104
+ encabezado,
105
+ pie,
106
+ }: {
107
+ etiqueta: string;
108
+ abierto: boolean;
109
+ onCerrar: () => void;
110
+ texto: string;
111
+ onBuscar: (texto: string) => void;
112
+ cargando?: boolean;
113
+ opciones: T[];
114
+ marcada: (opcion: T) => boolean;
115
+ onTocar: (opcion: T) => void;
116
+ detalle?: (opcion: T) => string | null;
117
+ grupo?: (opcion: T) => string;
118
+ vacio?: string;
119
+ encabezado?: React.ReactElement | null;
120
+ pie?: React.ReactNode;
121
+ }) {
122
+ const c = usePaleta();
123
+ const styles = useStyles();
124
+ const insets = useSafeAreaInsets();
125
+
126
+ return (
127
+ <Modal visible={abierto} animationType="slide" onRequestClose={onCerrar}>
128
+ <View style={[styles.hoja, { paddingTop: insets.top + 10 }]}>
129
+ <View style={styles.cabecera}>
130
+ <TouchableOpacity onPress={onCerrar}>
131
+ <MaterialIcons name="arrow-back" size={45} color={c.primary} />
132
+ </TouchableOpacity>
133
+ <Text style={styles.titulo}>{etiqueta}</Text>
134
+ </View>
135
+ <TextInput
136
+ value={texto}
137
+ onChangeText={onBuscar}
138
+ placeholder="Buscar"
139
+ placeholderTextColor={c.secondaryLight}
140
+ autoCorrect={false}
141
+ autoCapitalize="characters"
142
+ style={styles.buscador}
143
+ />
144
+ {cargando ? <ActivityIndicator color={c.primary} /> : null}
145
+ <FlatList
146
+ data={opciones}
147
+ keyExtractor={(opcion) => String(opcion.value)}
148
+ keyboardShouldPersistTaps="handled"
149
+ contentContainerStyle={{
150
+ gap: 10,
151
+ paddingBottom: pie ? 24 : insets.bottom + 24,
152
+ }}
153
+ ListHeaderComponent={encabezado}
154
+ ListEmptyComponent={
155
+ cargando ? null : <EstadoVacio icono="magnify" titulo={vacio} />
156
+ }
157
+ renderItem={({ item, index }) => {
158
+ const rotulo = grupo?.(item);
159
+ const nuevoGrupo =
160
+ rotulo !== undefined &&
161
+ (index === 0 || grupo?.(opciones[index - 1]) !== rotulo);
162
+ const elegida = marcada(item);
163
+ return (
164
+ <>
165
+ {nuevoGrupo ? <Text style={styles.grupo}>{rotulo}</Text> : null}
166
+ <Pressable
167
+ onPress={() => onTocar(item)}
168
+ accessibilityState={{ selected: elegida }}
169
+ style={[styles.opcion, elegida && styles.opcionElegida]}
170
+ >
171
+ <Text style={styles.opcionNombre}>{item.label}</Text>
172
+ {detalle?.(item) ? (
173
+ <Text style={styles.detalle}>{detalle(item)}</Text>
174
+ ) : null}
175
+ </Pressable>
176
+ </>
177
+ );
178
+ }}
179
+ />
180
+ {pie ? (
181
+ <View style={[styles.pie, { paddingBottom: insets.bottom + 16 }]}>{pie}</View>
182
+ ) : null}
183
+ </View>
184
+ </Modal>
185
+ );
186
+ }
187
+
188
+ /** El renglón cerrado de un selector: el rótulo, lo elegido y la flecha. */
189
+ function RenglonDelSelector({
190
+ etiqueta,
191
+ texto,
192
+ detalle,
193
+ disabled,
194
+ onPress,
195
+ }: {
196
+ etiqueta: string;
197
+ texto: string | null;
198
+ detalle?: string | null;
199
+ disabled?: boolean;
200
+ onPress: () => void;
201
+ }) {
202
+ const c = usePaleta();
203
+ const styles = useStyles();
204
+ return (
205
+ <Pressable
206
+ onPress={onPress}
207
+ disabled={disabled}
208
+ accessibilityRole="button"
209
+ style={[styles.campo, disabled && styles.campoInactivo]}
210
+ >
211
+ <Text style={styles.etiqueta}>{etiqueta}</Text>
212
+ <View style={styles.renglon}>
213
+ <Text style={[styles.valor, !texto && styles.sinValor]}>{texto ?? "Elegir…"}</Text>
214
+ <IconoDeRenglon
215
+ name="chevron-down"
216
+ fontSize={tipografia.subtitulo}
217
+ color={c.secondary}
218
+ />
219
+ </View>
220
+ {detalle ? <Text style={styles.detalle}>{detalle}</Text> : null}
221
+ </Pressable>
222
+ );
223
+ }
224
+
225
+ /**
226
+ * Un campo que se elige de una lista: el renglón con lo elegido y, al tocarlo, la lista a pantalla
227
+ * completa con su buscador. **A pantalla completa y no un menú**: con la letra al 180% un menú
228
+ * desplegable deja ver dos opciones, y quien la usa elige de pie.
229
+ */
230
+ export default function SelectorDeOpcion<T extends Opcion>(props: PropsDeSelectorDeOpcion<T>) {
231
+ const {
232
+ etiqueta,
233
+ valor,
234
+ onElegir,
235
+ cargando,
236
+ detalle,
237
+ grupo,
238
+ opcional,
239
+ textoDeNinguno = "Ninguno",
240
+ vacio,
241
+ disabled,
242
+ } = props;
243
+ const styles = useStyles();
244
+ const [abierto, setAbierto] = React.useState(false);
245
+ const { texto, buscar, visibles } = useBuscador(props);
246
+
247
+ const elegir = (opcion: T | null) => {
248
+ onElegir(opcion);
249
+ setAbierto(false);
250
+ buscar("");
251
+ };
252
+
253
+ return (
254
+ <>
255
+ <RenglonDelSelector
256
+ etiqueta={etiqueta}
257
+ texto={valor ? valor.label : null}
258
+ detalle={valor ? detalle?.(valor) : null}
259
+ disabled={disabled}
260
+ onPress={() => setAbierto(true)}
261
+ />
262
+ <HojaDeOpciones
263
+ etiqueta={etiqueta}
264
+ abierto={abierto}
265
+ onCerrar={() => setAbierto(false)}
266
+ texto={texto}
267
+ onBuscar={buscar}
268
+ cargando={cargando}
269
+ opciones={visibles}
270
+ marcada={(opcion) => valor?.value === opcion.value}
271
+ onTocar={elegir}
272
+ detalle={detalle}
273
+ grupo={grupo}
274
+ vacio={vacio}
275
+ encabezado={
276
+ opcional ? (
277
+ <Pressable onPress={() => elegir(null)} style={styles.opcion}>
278
+ <Text style={styles.opcionNombre}>{textoDeNinguno}</Text>
279
+ </Pressable>
280
+ ) : null
281
+ }
282
+ />
283
+ </>
284
+ );
285
+ }
286
+
287
+ /**
288
+ * Lo mismo que `SelectorDeOpcion`, pero se marcan varias: tocar una la pone o la quita, y la
289
+ * elección se confirma con el botón del pie. Se confirma y no se guarda a cada toque para que
290
+ * salir con la flecha deje lo que había —igual que en el selector de una, donde salir no cambia
291
+ * nada—.
292
+ *
293
+ * Cerrado dice lo elegido separado por comas, que con la letra grande parte en los renglones que
294
+ * haga falta en vez de recortarse.
295
+ */
296
+ export function SelectorDeVarias<T extends Opcion>(props: PropsDeSelectorDeVarias<T>) {
297
+ const { etiqueta, valor, onElegir, cargando, detalle, grupo, vacio, disabled } = props;
298
+ const [abierto, setAbierto] = React.useState(false);
299
+ const [marcadas, setMarcadas] = React.useState<T[]>(valor);
300
+ const { texto, buscar, visibles } = useBuscador(props);
301
+
302
+ const abrir = () => {
303
+ setMarcadas(valor);
304
+ setAbierto(true);
305
+ };
306
+ const cerrar = () => {
307
+ setAbierto(false);
308
+ buscar("");
309
+ };
310
+ const tocar = (opcion: T) =>
311
+ setMarcadas((previas) =>
312
+ previas.some((p) => p.value === opcion.value)
313
+ ? previas.filter((p) => p.value !== opcion.value)
314
+ : [...previas, opcion],
315
+ );
316
+
317
+ return (
318
+ <>
319
+ <RenglonDelSelector
320
+ etiqueta={etiqueta}
321
+ texto={valor.length ? valor.map((o) => o.label).join(", ") : null}
322
+ disabled={disabled}
323
+ onPress={abrir}
324
+ />
325
+ <HojaDeOpciones
326
+ etiqueta={etiqueta}
327
+ abierto={abierto}
328
+ onCerrar={cerrar}
329
+ texto={texto}
330
+ onBuscar={buscar}
331
+ cargando={cargando}
332
+ opciones={visibles}
333
+ marcada={(opcion) => marcadas.some((m) => m.value === opcion.value)}
334
+ onTocar={tocar}
335
+ detalle={detalle}
336
+ grupo={grupo}
337
+ vacio={vacio}
338
+ pie={
339
+ <BotonGrande
340
+ icono="check"
341
+ onPress={() => {
342
+ onElegir(marcadas);
343
+ cerrar();
344
+ }}
345
+ >
346
+ {marcadas.length ? `Listo (${marcadas.length})` : "Listo"}
347
+ </BotonGrande>
348
+ }
349
+ />
350
+ </>
351
+ );
352
+ }
353
+
354
+ const useStyles = crearEstilos((c) => ({
355
+ campo: {
356
+ backgroundColor: c.superficie,
357
+ borderRadius: 14,
358
+ borderWidth: 2,
359
+ borderColor: c.secondaryLighter,
360
+ padding: 14,
361
+ gap: 4,
362
+ },
363
+ campoInactivo: { opacity: 0.5 },
364
+ etiqueta: {
365
+ ...renglon(tipografia.etiqueta),
366
+ fontWeight: "800",
367
+ color: c.secondary,
368
+ textTransform: "uppercase",
369
+ letterSpacing: 0.8,
370
+ },
371
+ renglon: { flexDirection: "row", alignItems: "flex-start", gap: 8 },
372
+ valor: { flex: 1, ...renglon(tipografia.subtitulo), fontWeight: "800", color: c.texto },
373
+ sinValor: { color: c.secondaryLight, fontWeight: "600" },
374
+ detalle: { ...renglon(tipografia.secundario), color: c.secondary },
375
+ hoja: { flex: 1, backgroundColor: c.fondo, paddingHorizontal: 18, gap: 12 },
376
+ cabecera: { flexDirection: "row", alignItems: "center", gap: 8 },
377
+ titulo: { flex: 1, fontSize: tipografia.titulo, fontWeight: "900", color: c.primary },
378
+ buscador: {
379
+ borderWidth: 1.5,
380
+ borderColor: c.secondaryLight,
381
+ borderRadius: 12,
382
+ paddingHorizontal: 14,
383
+ paddingVertical: 12,
384
+ backgroundColor: c.superficie,
385
+ fontSize: tipografia.subtitulo,
386
+ color: c.texto,
387
+ },
388
+ grupo: {
389
+ ...renglon(tipografia.etiqueta),
390
+ fontWeight: "800",
391
+ color: c.secondary,
392
+ textTransform: "uppercase",
393
+ letterSpacing: 0.8,
394
+ marginTop: 8,
395
+ },
396
+ opcion: {
397
+ backgroundColor: c.superficie,
398
+ borderRadius: 14,
399
+ borderWidth: 2,
400
+ borderColor: c.secondaryLighter,
401
+ padding: 14,
402
+ gap: 4,
403
+ },
404
+ opcionElegida: { borderColor: c.primary, backgroundColor: c.primaryWash },
405
+ opcionNombre: { ...renglon(tipografia.subtitulo), fontWeight: "800", color: c.texto },
406
+ pie: { paddingTop: 12 },
407
+ }));
@@ -0,0 +1,72 @@
1
+ import { useState } from "react";
2
+ import { Controller, type FieldValues } from "react-hook-form";
3
+ import { View } from "react-native";
4
+ import { HelperText, TextInput, type TextInputProps } from "react-native-paper";
5
+ import { crearEstilos } from "../tema";
6
+ import type { CampoAtado } from "./controlados";
7
+
8
+ /**
9
+ * Un campo del login (el usuario, la contraseña), atado a react-hook-form: el `TextInput` de Paper
10
+ * con contorno y la etiqueta que sube al escribir. Con `contrasena`, oculta lo escrito y lleva el
11
+ * ojo para verlo.
12
+ *
13
+ * Es de Paper y no `TextoControlado`, que lleva su rótulo arriba, porque así es la pantalla de
14
+ * entrada de las apps.
15
+ */
16
+ export default function TextoDeAccesoControlado<T extends FieldValues>({
17
+ control,
18
+ name,
19
+ etiqueta,
20
+ contrasena = false,
21
+ entrada,
22
+ }: CampoAtado<T> & {
23
+ etiqueta: string;
24
+ contrasena?: boolean;
25
+ /** Lo demás del `TextInput` de Paper (`autoCapitalize`, `keyboardType`…). */
26
+ entrada?: Partial<TextInputProps>;
27
+ }) {
28
+ const styles = useStyles();
29
+ const [verla, setVerla] = useState(false);
30
+
31
+ return (
32
+ <Controller
33
+ name={name}
34
+ control={control}
35
+ render={({ field: { onChange, value }, fieldState: { error } }) => (
36
+ <View>
37
+ <TextInput
38
+ label={etiqueta}
39
+ value={value}
40
+ onChangeText={onChange}
41
+ mode={"outlined"}
42
+ error={!!error}
43
+ secureTextEntry={contrasena && !verla}
44
+ right={
45
+ contrasena ? (
46
+ <TextInput.Icon
47
+ icon={verla ? "eye-off" : "eye"}
48
+ onPress={() => setVerla(!verla)}
49
+ />
50
+ ) : undefined
51
+ }
52
+ outlineStyle={styles.contorno}
53
+ style={styles.caja}
54
+ contentStyle={styles.contenido}
55
+ {...entrada}
56
+ />
57
+ {error ? (
58
+ <HelperText type={"error"} visible>
59
+ {error.message}
60
+ </HelperText>
61
+ ) : null}
62
+ </View>
63
+ )}
64
+ />
65
+ );
66
+ }
67
+
68
+ const useStyles = crearEstilos((c) => ({
69
+ contorno: { backgroundColor: c.superficie, borderRadius: 12 },
70
+ caja: { height: 64 },
71
+ contenido: { fontSize: 18 },
72
+ }));