🎨 Styling and Design Guide

Sistema de Diseño

Colores

// constants/colors.ts
export const Colors = {
  // Primarios
  primary: '#0A84FF',      // Azul principal
  primaryLight: '#3D9FFF',
  primaryDark: '#0066CC',
  
  // Secundarios
  secondary: '#5E5CE6',    // Púrpura
  accent: '#FF6B35',       // Naranja
  
  // Estados
  success: '#30D158',
  warning: '#FF9F0A',
  error: '#FF453A',
  
  // Fondos
  background: '#F8F9FA',
  surface: '#FFFFFF',
  surfaceSecondary: '#F2F3F5',
  
  // Texto
  text: '#1A1A2E',
  textSecondary: '#6B7280',
  textTertiary: '#9CA3AF',
  textInverse: '#FFFFFF',
  
  // Bordes
  border: '#E5E7EB',
  borderLight: '#F3F4F6',
};

Espaciado

export const Spacing = {
  xs: 4,
  sm: 8,
  md: 12,
  lg: 16,
  xl: 20,
  xxl: 24,
  xxxl: 32,
};

// Uso
padding: Spacing.md,      // 12
marginVertical: Spacing.lg, // 16
gap: Spacing.sm,          // 8

Border Radius

export const BorderRadius = {
  sm: 6,
  md: 10,
  lg: 14,
  xl: 20,
  full: 9999,  // Para círculos
};

// Uso
borderRadius: BorderRadius.md,  // 10
borderRadius: BorderRadius.full, // Circular

Tipografía

export const FontSizes = {
  xs: 11,
  sm: 13,
  md: 15,
  lg: 17,
  xl: 20,
  xxl: 24,
  xxxl: 32,
  display: 40,
};

export const FontWeights = {
  regular: '400' as const,
  medium: '500' as const,
  semibold: '600' as const,
  bold: '700' as const,
};

// Uso
fontSize: FontSizes.lg,
fontWeight: FontWeights.semibold,

Patrones de Estilos

Contenedor Base

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Colors.background,
  },
  content: {
    padding: Spacing.lg,
  },
});

Card

const styles = StyleSheet.create({
  card: {
    backgroundColor: Colors.surface,
    borderRadius: BorderRadius.lg,
    padding: Spacing.md,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.06,
    shadowRadius: 8,
    elevation: 3,
  },
});

Lista con Separadores

const styles = StyleSheet.create({
  listItem: {
    paddingVertical: Spacing.md,
    paddingHorizontal: Spacing.lg,
    borderBottomWidth: 1,
    borderBottomColor: Colors.borderLight,
  },
});

Header Personalizado

const styles = StyleSheet.create({
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    paddingHorizontal: Spacing.lg,
    paddingVertical: Spacing.md,
    backgroundColor: Colors.surface,
    borderBottomWidth: 1,
    borderBottomColor: Colors.borderLight,
  },
});

Gradientes

import { LinearGradient } from 'expo-linear-gradient';

<LinearGradient
  colors={Colors.gradients.primary}
  start={{ x: 0, y: 0 }}
  end={{ x: 1, y: 0 }}
  style={styles.gradientButton}
>
  <Text style={styles.buttonText}>Gradient Button</Text>
</LinearGradient>

// Gradientes disponibles
Colors.gradients.primary  // Azul a púrpura
Colors.gradients.sunset   // Naranja a amarillo
Colors.gradients.ocean    // Azul a verde
Colors.gradients.purple   // Púrpura a rosa

Sombras

// Sombra sutil (cards)
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.06,
shadowRadius: 8,
elevation: 3,

// Sombra media (modales, dropdowns)
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 12,
elevation: 5,

// Sombra fuerte (FAB, elementos flotantes)
shadowColor: '#000',
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.15,
shadowRadius: 16,
elevation: 8,

Animaciones con Animated API

Fade In

import { Animated } from 'react-native';

function FadeInView({ children }) {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true,
    }).start();
  }, []);

  return (
    <Animated.View style={{ opacity: fadeAnim }}>
      {children}
    </Animated.View>
  );
}

Scale on Press

function ScaleButton({ onPress, children }) {
  const scale = useRef(new Animated.Value(1)).current;

  const handlePressIn = () => {
    Animated.spring(scale, {
      toValue: 0.95,
      useNativeDriver: true,
    }).start();
  };

  const handlePressOut = () => {
    Animated.spring(scale, {
      toValue: 1,
      useNativeDriver: true,
    }).start();
  };

  return (
    <TouchableOpacity
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      onPress={onPress}
      activeOpacity={1}
    >
      <Animated.View style={{ transform: [{ scale }] }}>
        {children}
      </Animated.View>
    </TouchableOpacity>
  );
}

Dark Mode

// Usar el ThemeContext
const { isDark, colors } = useTheme();

const styles = StyleSheet.create({
  container: {
    backgroundColor: isDark ? colors.background : colors.backgroundLight,
  },
  text: {
    color: isDark ? colors.textDark : colors.text,
  },
});

Responsive Design

import { Dimensions, useWindowDimensions } from 'react-native';

function ResponsiveGrid() {
  const { width } = useWindowDimensions();
  const numColumns = width > 600 ? 3 : 2;
  const itemWidth = (width - Spacing.lg * (numColumns + 1)) / numColumns;

  return (
    <FlatList
      data={items}
      numColumns={numColumns}
      key={numColumns} // Importante para re-render
      renderItem={({ item }) => (
        <Card style={{ width: itemWidth }} item={item} />
      )}
    />
  );
}

Best Practices

  1. Usar constantes - Nunca hardcodear colores o espaciados
  2. Consistencia - Mantener el mismo espaciado en toda la app
  3. Accesibilidad - Contraste mínimo de 4.5:1 para texto
  4. Performance - Usar useNativeDriver: true en animaciones
  5. Modularidad - Crear componentes para estilos repetidos