Structure: - control-plane/: Registries, SIMCO directives, CI/CD templates - projects/: Gamilit, ERP-Suite, Trading-Platform, Betting-Analytics - shared/: Libs catalog, knowledge-base Key features: - Centralized port, domain, database, and service registries - 23 SIMCO directives + 6 fundamental principles - NEXUS agent profiles with delegation rules - Validation scripts for workspace integrity - Dockerfiles for all services - Path aliases for quick reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
521 lines
10 KiB
Markdown
521 lines
10 KiB
Markdown
# Guía de Componentes UI
|
|
|
|
**Versión:** 1.0.0
|
|
**Última Actualización:** 2025-11-28
|
|
**Aplica a:** apps/frontend/src/shared/components/ui/
|
|
|
|
---
|
|
|
|
## Resumen
|
|
|
|
GAMILIT utiliza una librería de componentes UI propios construidos sobre Tailwind CSS y Radix UI primitives. Esta guía documenta los componentes disponibles y cómo utilizarlos.
|
|
|
|
---
|
|
|
|
## Stack de UI
|
|
|
|
- **Tailwind CSS**: Estilos utilitarios
|
|
- **Radix UI**: Primitivos accesibles (Dialog, Dropdown, etc.)
|
|
- **CVA (class-variance-authority)**: Variantes de componentes
|
|
- **Lucide Icons**: Iconografía
|
|
- **Sonner**: Toasts/Notificaciones
|
|
|
|
---
|
|
|
|
## Componentes Base
|
|
|
|
### Button
|
|
|
|
```typescript
|
|
import { Button } from '@/shared/components/ui';
|
|
|
|
// Variantes
|
|
<Button variant="primary">Primario</Button>
|
|
<Button variant="secondary">Secundario</Button>
|
|
<Button variant="outline">Outline</Button>
|
|
<Button variant="ghost">Ghost</Button>
|
|
<Button variant="danger">Peligro</Button>
|
|
|
|
// Tamaños
|
|
<Button size="sm">Pequeño</Button>
|
|
<Button size="md">Mediano</Button>
|
|
<Button size="lg">Grande</Button>
|
|
|
|
// Estados
|
|
<Button disabled>Deshabilitado</Button>
|
|
<Button isLoading>Cargando...</Button>
|
|
|
|
// Con icono
|
|
<Button>
|
|
<PlusIcon className="w-4 h-4 mr-2" />
|
|
Agregar
|
|
</Button>
|
|
```
|
|
|
|
### Input
|
|
|
|
```typescript
|
|
import { Input } from '@/shared/components/ui';
|
|
|
|
// Básico
|
|
<Input placeholder="Escribe aquí..." />
|
|
|
|
// Con label
|
|
<div>
|
|
<label htmlFor="email">Email</label>
|
|
<Input id="email" type="email" />
|
|
</div>
|
|
|
|
// Con error
|
|
<Input error="Este campo es requerido" />
|
|
|
|
// Variantes
|
|
<Input variant="filled" />
|
|
<Input variant="outlined" />
|
|
|
|
// Iconos
|
|
<Input leftIcon={<SearchIcon />} placeholder="Buscar..." />
|
|
<Input rightIcon={<EyeIcon />} type="password" />
|
|
```
|
|
|
|
### Card
|
|
|
|
```typescript
|
|
import { Card, CardHeader, CardBody, CardFooter } from '@/shared/components/ui';
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<h3>Título de la tarjeta</h3>
|
|
</CardHeader>
|
|
<CardBody>
|
|
Contenido de la tarjeta...
|
|
</CardBody>
|
|
<CardFooter>
|
|
<Button>Acción</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
|
|
// Variantes
|
|
<Card variant="elevated">Con sombra</Card>
|
|
<Card variant="outlined">Con borde</Card>
|
|
<Card variant="filled">Fondo sólido</Card>
|
|
|
|
// Interactiva
|
|
<Card isHoverable onClick={handleClick}>
|
|
Tarjeta clickeable
|
|
</Card>
|
|
```
|
|
|
|
### Badge
|
|
|
|
```typescript
|
|
import { Badge } from '@/shared/components/ui';
|
|
|
|
// Colores
|
|
<Badge>Default</Badge>
|
|
<Badge color="success">Completado</Badge>
|
|
<Badge color="warning">Pendiente</Badge>
|
|
<Badge color="danger">Error</Badge>
|
|
<Badge color="info">Nuevo</Badge>
|
|
|
|
// Tamaños
|
|
<Badge size="sm">Pequeño</Badge>
|
|
<Badge size="md">Mediano</Badge>
|
|
|
|
// Con ícono
|
|
<Badge>
|
|
<StarIcon className="w-3 h-3 mr-1" />
|
|
Premium
|
|
</Badge>
|
|
```
|
|
|
|
### Avatar
|
|
|
|
```typescript
|
|
import { Avatar, AvatarGroup } from '@/shared/components/ui';
|
|
|
|
// Básico
|
|
<Avatar src={user.avatarUrl} alt={user.name} />
|
|
|
|
// Fallback con iniciales
|
|
<Avatar fallback="JD" />
|
|
|
|
// Tamaños
|
|
<Avatar size="sm" src={...} /> {/* 32px */}
|
|
<Avatar size="md" src={...} /> {/* 40px */}
|
|
<Avatar size="lg" src={...} /> {/* 56px */}
|
|
|
|
// Grupo de avatares
|
|
<AvatarGroup max={3}>
|
|
<Avatar src={user1.avatar} />
|
|
<Avatar src={user2.avatar} />
|
|
<Avatar src={user3.avatar} />
|
|
<Avatar src={user4.avatar} /> {/* +1 */}
|
|
</AvatarGroup>
|
|
```
|
|
|
|
---
|
|
|
|
## Componentes de Formulario
|
|
|
|
### Select
|
|
|
|
```typescript
|
|
import { Select, SelectOption } from '@/shared/components/ui';
|
|
|
|
<Select
|
|
label="Categoría"
|
|
value={selected}
|
|
onChange={setSelected}
|
|
placeholder="Selecciona una opción"
|
|
>
|
|
<SelectOption value="1">Opción 1</SelectOption>
|
|
<SelectOption value="2">Opción 2</SelectOption>
|
|
<SelectOption value="3">Opción 3</SelectOption>
|
|
</Select>
|
|
```
|
|
|
|
### Checkbox
|
|
|
|
```typescript
|
|
import { Checkbox } from '@/shared/components/ui';
|
|
|
|
<Checkbox
|
|
checked={isChecked}
|
|
onChange={setIsChecked}
|
|
label="Acepto los términos"
|
|
/>
|
|
|
|
// Sin label
|
|
<Checkbox checked={...} onChange={...} />
|
|
```
|
|
|
|
### Switch
|
|
|
|
```typescript
|
|
import { Switch } from '@/shared/components/ui';
|
|
|
|
<Switch
|
|
checked={isEnabled}
|
|
onChange={setIsEnabled}
|
|
label="Notificaciones"
|
|
/>
|
|
```
|
|
|
|
### Form Field
|
|
|
|
```typescript
|
|
import { FormField, Input, FormError, FormHelper } from '@/shared/components/ui';
|
|
|
|
<FormField>
|
|
<label>Email</label>
|
|
<Input type="email" {...register('email')} />
|
|
<FormHelper>Usaremos este email para contactarte</FormHelper>
|
|
{errors.email && <FormError>{errors.email.message}</FormError>}
|
|
</FormField>
|
|
```
|
|
|
|
---
|
|
|
|
## Componentes de Feedback
|
|
|
|
### Modal / Dialog
|
|
|
|
```typescript
|
|
import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/shared/components/ui';
|
|
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
<Button onClick={() => setIsOpen(true)}>Abrir Modal</Button>
|
|
|
|
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
|
<ModalHeader>
|
|
<h2>Confirmar acción</h2>
|
|
</ModalHeader>
|
|
<ModalBody>
|
|
¿Estás seguro de que deseas continuar?
|
|
</ModalBody>
|
|
<ModalFooter>
|
|
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
|
Cancelar
|
|
</Button>
|
|
<Button onClick={handleConfirm}>
|
|
Confirmar
|
|
</Button>
|
|
</ModalFooter>
|
|
</Modal>
|
|
```
|
|
|
|
### Toast
|
|
|
|
```typescript
|
|
import { toast } from 'sonner';
|
|
|
|
// Éxito
|
|
toast.success('Guardado correctamente');
|
|
|
|
// Error
|
|
toast.error('Ocurrió un error');
|
|
|
|
// Info
|
|
toast.info('Nueva notificación');
|
|
|
|
// Con acción
|
|
toast('Archivo eliminado', {
|
|
action: {
|
|
label: 'Deshacer',
|
|
onClick: () => undoDelete(),
|
|
},
|
|
});
|
|
|
|
// Promesa
|
|
toast.promise(saveData(), {
|
|
loading: 'Guardando...',
|
|
success: 'Guardado',
|
|
error: 'Error al guardar',
|
|
});
|
|
```
|
|
|
|
### LoadingSpinner
|
|
|
|
```typescript
|
|
import { LoadingSpinner } from '@/shared/components/ui';
|
|
|
|
// Básico
|
|
<LoadingSpinner />
|
|
|
|
// Tamaños
|
|
<LoadingSpinner size="sm" />
|
|
<LoadingSpinner size="md" />
|
|
<LoadingSpinner size="lg" />
|
|
|
|
// Centrado en página
|
|
<div className="flex items-center justify-center h-screen">
|
|
<LoadingSpinner size="lg" />
|
|
</div>
|
|
```
|
|
|
|
### EmptyState
|
|
|
|
```typescript
|
|
import { EmptyState } from '@/shared/components/ui';
|
|
|
|
<EmptyState
|
|
icon={<InboxIcon className="w-12 h-12" />}
|
|
title="No hay resultados"
|
|
description="No encontramos ejercicios que coincidan con tu búsqueda"
|
|
action={
|
|
<Button onClick={clearFilters}>Limpiar filtros</Button>
|
|
}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Componentes de Navegación
|
|
|
|
### Tabs
|
|
|
|
```typescript
|
|
import { Tabs, TabList, Tab, TabPanel } from '@/shared/components/ui';
|
|
|
|
<Tabs defaultValue="tab1">
|
|
<TabList>
|
|
<Tab value="tab1">General</Tab>
|
|
<Tab value="tab2">Avanzado</Tab>
|
|
<Tab value="tab3">Configuración</Tab>
|
|
</TabList>
|
|
|
|
<TabPanel value="tab1">
|
|
Contenido de General
|
|
</TabPanel>
|
|
<TabPanel value="tab2">
|
|
Contenido de Avanzado
|
|
</TabPanel>
|
|
<TabPanel value="tab3">
|
|
Contenido de Configuración
|
|
</TabPanel>
|
|
</Tabs>
|
|
```
|
|
|
|
### Dropdown
|
|
|
|
```typescript
|
|
import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from '@/shared/components/ui';
|
|
|
|
<Dropdown>
|
|
<DropdownTrigger>
|
|
<Button variant="outline">
|
|
Opciones <ChevronDownIcon />
|
|
</Button>
|
|
</DropdownTrigger>
|
|
<DropdownContent>
|
|
<DropdownItem onClick={handleEdit}>Editar</DropdownItem>
|
|
<DropdownItem onClick={handleDuplicate}>Duplicar</DropdownItem>
|
|
<DropdownItem onClick={handleDelete} variant="danger">
|
|
Eliminar
|
|
</DropdownItem>
|
|
</DropdownContent>
|
|
</Dropdown>
|
|
```
|
|
|
|
### Pagination
|
|
|
|
```typescript
|
|
import { Pagination } from '@/shared/components/ui';
|
|
|
|
<Pagination
|
|
currentPage={page}
|
|
totalPages={totalPages}
|
|
onPageChange={setPage}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Componentes de Datos
|
|
|
|
### Table
|
|
|
|
```typescript
|
|
import { Table, TableHead, TableBody, TableRow, TableCell } from '@/shared/components/ui';
|
|
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>Nombre</TableCell>
|
|
<TableCell>Email</TableCell>
|
|
<TableCell>Rol</TableCell>
|
|
<TableCell align="right">Acciones</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{users.map((user) => (
|
|
<TableRow key={user.id}>
|
|
<TableCell>{user.name}</TableCell>
|
|
<TableCell>{user.email}</TableCell>
|
|
<TableCell><Badge>{user.role}</Badge></TableCell>
|
|
<TableCell align="right">
|
|
<Button size="sm" variant="ghost">Editar</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
```
|
|
|
|
### Progress Bar
|
|
|
|
```typescript
|
|
import { Progress } from '@/shared/components/ui';
|
|
|
|
// Básico
|
|
<Progress value={75} />
|
|
|
|
// Con label
|
|
<Progress value={75} showLabel />
|
|
|
|
// Colores
|
|
<Progress value={100} color="success" />
|
|
<Progress value={50} color="warning" />
|
|
<Progress value={20} color="danger" />
|
|
|
|
// XP Progress (específico de GAMILIT)
|
|
<XpProgressBar
|
|
currentXp={450}
|
|
maxXp={500}
|
|
level={2}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Componentes de GAMILIT
|
|
|
|
### RankBadge
|
|
|
|
```typescript
|
|
import { RankBadge } from '@/features/gamification';
|
|
|
|
<RankBadge rank={userRank} />
|
|
<RankBadge rank={userRank} size="lg" showName />
|
|
```
|
|
|
|
### MlCoinsDisplay
|
|
|
|
```typescript
|
|
import { MlCoinsDisplay } from '@/features/gamification';
|
|
|
|
<MlCoinsDisplay coins={250} />
|
|
<MlCoinsDisplay coins={250} animated /> // Animación al cambiar
|
|
```
|
|
|
|
### AchievementCard
|
|
|
|
```typescript
|
|
import { AchievementCard } from '@/features/gamification';
|
|
|
|
<AchievementCard
|
|
achievement={achievement}
|
|
userProgress={userAchievement}
|
|
onClaim={handleClaim}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Theming
|
|
|
|
### Variables CSS
|
|
|
|
```css
|
|
/* shared/styles/variables.css */
|
|
:root {
|
|
--color-primary: #3b82f6;
|
|
--color-primary-dark: #2563eb;
|
|
--color-secondary: #8b5cf6;
|
|
--color-success: #22c55e;
|
|
--color-warning: #f59e0b;
|
|
--color-danger: #ef4444;
|
|
|
|
--radius-sm: 0.25rem;
|
|
--radius-md: 0.5rem;
|
|
--radius-lg: 0.75rem;
|
|
}
|
|
```
|
|
|
|
### Tailwind Config
|
|
|
|
```javascript
|
|
// tailwind.config.js
|
|
module.exports = {
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
primary: 'var(--color-primary)',
|
|
'primary-dark': 'var(--color-primary-dark)',
|
|
secondary: 'var(--color-secondary)',
|
|
},
|
|
},
|
|
},
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Buenas Prácticas
|
|
|
|
1. **Usar componentes base**: No reinventar la rueda
|
|
2. **Props consistentes**: Mismos nombres en todos los componentes
|
|
3. **Variantes con CVA**: Para componentes con múltiples estilos
|
|
4. **Accesibilidad**: Usar ARIA labels y keyboard navigation
|
|
5. **Composición**: Preferir composición sobre props complejas
|
|
6. **Documentar**: Storybook para documentación visual
|
|
|
|
---
|
|
|
|
## Ver También
|
|
|
|
- [ESTRUCTURA-SHARED.md](./ESTRUCTURA-SHARED.md) - Ubicación de componentes
|
|
- [STATE-MANAGEMENT.md](./STATE-MANAGEMENT.md) - Estado en componentes
|