Skip to main content

UI y estilos

En BullStock conviven dos kits de UI. Saber cuál usar es lo primero que hay que resolver antes de escribir una pantalla.

CarpetaQué esCuándo usarlo
components/ui-native/Primitivas sobre HeroUI NativeLo nuevo va acá
components/ui/Primitivas generadas de GluestackSolo si tocás una pantalla que ya las usa. No las edites a mano: son generadas

Hoy la app importa @/components/ui/ en ~85 archivos y @/components/ui-native/ en ~10: la migración está en curso, no terminada.

La decisión

BullStock nació con NativeWind 4 (Tailwind v3) + Gluestack UI. Tailwind v4 y la new architecture de React Native dejaron atrás la config que Gluestack asume, y el kit generado es incómodo de mantener (no se puede editar sin perder los cambios en la próxima generación).

Se decidió migrar en dos ejes:

  1. Estilos → Uniwind (Tailwind v4). nativewind ya no es dependencia y no hay tailwind.config.js.
  2. Componentes → HeroUI Native, reemplazando gradualmente a Gluestack.

Migración gradual, no big-bang: son ~85 archivos de pantallas críticas sin tests automatizados; la validación es manual en dispositivo. Al tocar una pantalla, migrala.

Estilos con Uniwind

El entrypoint es global.css:

@import 'tailwindcss';
@import 'uniwind';
@import 'heroui-native/styles';

@source './app';
@source './components';
/* … */

@theme {
--color-primary-400: #6BB0ED; /* azul Oxitec */
--color-primary-500: #4A9AE8;
--color-secondary-500: #0A7FC9;
/* … */
}

Eso expone la paleta como utilities (bg-primary-500, text-secondary-700). No hardcodees hex.

El tema claro/oscuro se conmuta con Uniwind.setTheme() desde contexts/ThemeContext.tsx.

Las primitivas nuevas

Todo se exporta desde components/ui-native/index.ts:

Button · Chip · Dialog + AlertDialog · Input · SearchField · Skeleton (+ LegacySkeleton de compatibilidad) · Tabs + SimpleTabs · Toast + useShowToast · BottomSheet + ControlledBottomSheet.

Sueltos en la misma carpeta: AnimatedBlurView, DialogBlurBackdrop, HapticPressable, SafeAreaView, ScrollShadow.

El puente de la migración

components/shared/form/customButton.tsx envuelve el Button de ui-native y traduce la API vieja de Gluestack (action / variant / size) a la de HeroUI.

Seguí usándolo en vez de reescribir cada call-site: es lo que permite migrar sin tocar 85 archivos de una.

Componentes compartidos de dominio

components/shared/, organizado por categoría:

FormulariosSheetSelect (el selector estándar: trigger + modal a pantalla completa) · customSelect (el viejo, sobre Gluestack; preferí SheetSelect) · ProductSelector (selector de producto por elemento, con imágenes, cantidades y depósito — el compuesto más grande del kit).

FeedbackConfirmDialog · networkBanner · ErrorBoundary · loading / loading2 / customLoading · skeleton · carouselModal · updateRequiredModal.

CámaraCamera, ScanMask, ScanConfirmation, inventareableCameraScan, torchButton.

LayoutContentSheet · ResponsiveContainer · PressableScale (sus props scale* están @deprecated e ignoradas).

OtrosNativeHeader · drawer + logout-alert · fade-in-box / slide-in-box · externalImage · countdown / full-text · MapAppPicker · ModeSwitch · app-info · viewDatabase.

Lo específico de una feature va en components/roadmap/, components/inventory/, components/patient/, components/home/. Si sirve para más de una, subilo a shared/<categoría>/.

Gotchas de la convivencia

Los dos shims de Metro no se tocan

metro.config.js tiene dos redirecciones que existen solo por la convivencia de kits:

  • idblib/idb-shim.js — Firebase pide IndexedDB, que no existe en RN.
  • tailwindcss/resolveConfig y */tailwind.configlib/tailwind-shim.jsuseBreakPointValue de Gluestack importa APIs de Tailwind v3 que ya no existen.

Si los sacás sin terminar la migración, Metro no resuelve y la app no compila.

El trigger de SheetSelect usa el TouchableOpacity de gesture-handler

No es un descuido: dentro de un @gorhom/bottom-sheet en Android, el Pressable de RNGH a veces no dispara onPress. Los Pressable internos del modal sí son los de React Native, porque el Modal vive en una ventana nativa propia, fuera del sheet.

components/ui/gluestack-ui-provider/ está marcado @deprecated (el tema ya no se aplica ahí) pero el provider sigue montado, porque los componentes generados lo necesitan.

El CLAUDE.md del repo está desactualizado en esto

Dice "NativeWind 4", "Gluestack UI" y "theme extensions en tailwind.config.js". Ninguna de las tres es cierta hoy.

Bottom sheets con teclado en Android

Receta que funciona, por si la necesitás: usar el scrollable de @gorhom/bottom-sheet + contentContainerClassName='h-full' + los handlers de bottom-sheet en el input. El ejemplo real está en el body de agregar suministro de la pantalla de movimiento de stock del vehículo.