Skip to main content

Estilos y temas

E-Cashpoint Web usa styled-components con un sistema de themes por perfil de comercio.

ThemeProvider

El tema global se define en src/globalStyles.js y se inyecta con <ThemeProvider> en App.jsx:

import { ThemeProvider } from 'styled-components';
import { globalStyles } from './globalStyles';

<ThemeProvider theme={globalStyles}>
<App />
</ThemeProvider>

Estructura del theme

// src/globalStyles.js
export const globalStyles = {
colors: {
primary: '#3b82f6',
primaryLight: '#dbeafe',
secondary: '#8b5cf6',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
text: '#0f172a',
textSoft: '#64748b',
background: '#f5f7fa',
surface: '#ffffff',
surfaceAlt: '#f8fafc',
border: '#e2e8f0',
},
radii: {
sm: '6px',
md: '10px',
lg: '16px',
xl: '20px',
round: '999px',
},
shadows: {
sm: '0 1px 2px rgba(0,0,0,0.05)',
md: '0 4px 6px rgba(0,0,0,0.1)',
lg: '0 10px 15px rgba(0,0,0,0.1)',
xl: '0 20px 25px rgba(0,0,0,0.15)',
},
gradients: {
primary: 'linear-gradient(135deg, #3b82f6, #8b5cf6)',
hero: 'linear-gradient(135deg, #0f172a, #1e293b)',
},
};

Uso en componentes

import styled from 'styled-components';

const Button = styled.button`
background: ${({ theme }) => theme.colors.primary};
color: white;
padding: 12px 20px;
border-radius: ${({ theme }) => theme.radii.lg};
box-shadow: ${({ theme }) => theme.shadows.md};

&:hover {
background: ${({ theme }) => theme.colors.primaryLight};
}
`;

Themes por perfil

Cada perfil de comercio tiene su propio theme (colores, etc.):

// src/styles/profileThemes.js
export const getProfileTheme = (profile) => {
const themes = {
restaurantes: {
primary: '#F59E0B', // naranja
secondary: '#DC2626', // rojo
},
servicios: {
primary: '#8B5CF6', // púrpura
secondary: '#3B82F6', // azul
},
productos: {
primary: '#10B981', // verde
secondary: '#3B82F6', // azul
},
};
return themes[profile] || themes.productos;
};

Uso:

const profileTheme = getProfileTheme('restaurantes');
<Button $profileTheme={profileTheme}>Click</Button>

Patrones comunes

$variant para variantes

const Badge = styled.span`
background: ${({ $variant, theme }) => {
if ($variant === 'success') return theme.colors.successLight;
if ($variant === 'danger') return theme.colors.dangerLight;
return theme.colors.primaryLight;
}};
color: ${({ $variant, theme }) => {
if ($variant === 'success') return theme.colors.success;
if ($variant === 'danger') return theme.colors.danger;
return theme.colors.primary;
}};
`;

Responsive design

const Grid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 18px;

@media (max-width: 720px) {
grid-template-columns: 1fr;
gap: 14px;
}
`;

Animaciones con framer-motion

import { motion } from 'framer-motion';

<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Contenido
</motion.div>

Convenciones

  • camelCase para props de styled-components: $primaryColor, $variant
  • Prefijo $ para props transient (no se pasan al DOM)
  • Theme via ThemeProvider
  • Media queries dentro del styled-component
  • Hover/focus states con &:hover, &:focus
  • No uses inline styles (excepto para valores dinámicos como posiciones de gráficos)

Próximos pasos