---
description: Crear componente React organizado por tipo
---

# Crear Componente React

Este workflow crea un componente React/TypeScript siguiendo la organización por tipo.

## Parámetros Requeridos

- `{Tipo}`: Tipo de componente (tables, modals, search, forms, navigation, ui, feedback)
- `{Nombre}`: Nombre del componente en PascalCase (ej: DataTable, ConfirmModal)

## Tipos de Componentes Disponibles

| Tipo         | Descripción             | Ejemplos                                 |
| ------------ | ----------------------- | ---------------------------------------- |
| `tables`     | Tablas y paginación     | DataTable, TablePagination, TableFilters |
| `modals`     | Ventanas modales        | ConfirmModal, FormModal, AlertModal      |
| `search`     | Búsqueda y filtros      | SearchBar, SearchFilters, SearchResults  |
| `forms`      | Elementos de formulario | TextInput, SelectInput, SubmitButton     |
| `navigation` | Navegación              | Sidebar, Navbar, Breadcrumbs             |
| `ui`         | UI general (shadcn)     | Card, Badge, Tooltip, Avatar             |
| `feedback`   | Retroalimentación       | Alert, Toast, Spinner, Progress          |

## Pasos

### 1. Verificar que la carpeta existe

La carpeta debería existir en `resources/js/components/{tipo}/`.
Si no existe:

```bash
mkdir -p resources/js/components/{tipo}
```

### 2. Crear archivo del componente

Crear `resources/js/components/{tipo}/{nombre-kebab}.tsx`:

```tsx
import { type FC } from 'react';

interface {Nombre}Props {
    // Definir props aquí
}

export const {Nombre}: FC<{Nombre}Props> = ({}) => {
    return (
        <div>
            {/* Contenido del componente */}
        </div>
    );
};

export default {Nombre};
```

### 3. Exportar desde index (opcional)

Si el tipo tiene un `index.ts`, agregar la exportación:

```typescript
export { {Nombre} } from './{nombre-kebab}';
```

## Ejemplos por Tipo

### Table Component

```tsx
// resources/js/components/tables/data-table.tsx
import { type FC } from 'react';
import {
    Table,
    TableBody,
    TableCell,
    TableHead,
    TableHeader,
    TableRow,
} from '@/components/ui/table';

interface Column<T> {
    key: keyof T;
    header: string;
    render?: (value: T[keyof T], row: T) => React.ReactNode;
}

interface DataTableProps<T> {
    data: T[];
    columns: Column<T>[];
    loading?: boolean;
}

export function DataTable<T>({ data, columns, loading }: DataTableProps<T>) {
    if (loading) {
        return <div className="animate-pulse">Cargando...</div>;
    }

    return (
        <Table>
            <TableHeader>
                <TableRow>
                    {columns.map((col) => (
                        <TableHead key={String(col.key)}>
                            {col.header}
                        </TableHead>
                    ))}
                </TableRow>
            </TableHeader>
            <TableBody>
                {data.map((row, idx) => (
                    <TableRow key={idx}>
                        {columns.map((col) => (
                            <TableCell key={String(col.key)}>
                                {col.render
                                    ? col.render(row[col.key], row)
                                    : String(row[col.key])}
                            </TableCell>
                        ))}
                    </TableRow>
                ))}
            </TableBody>
        </Table>
    );
}
```

### Modal Component

```tsx
// resources/js/components/modals/confirm-modal.tsx
import { type FC } from 'react';
import {
    Dialog,
    DialogContent,
    DialogDescription,
    DialogFooter,
    DialogHeader,
    DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';

interface ConfirmModalProps {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    title: string;
    description: string;
    onConfirm: () => void;
    confirmText?: string;
    cancelText?: string;
    variant?: 'default' | 'destructive';
}

export const ConfirmModal: FC<ConfirmModalProps> = ({
    open,
    onOpenChange,
    title,
    description,
    onConfirm,
    confirmText = 'Confirmar',
    cancelText = 'Cancelar',
    variant = 'default',
}) => {
    return (
        <Dialog open={open} onOpenChange={onOpenChange}>
            <DialogContent>
                <DialogHeader>
                    <DialogTitle>{title}</DialogTitle>
                    <DialogDescription>{description}</DialogDescription>
                </DialogHeader>
                <DialogFooter>
                    <Button
                        variant="outline"
                        onClick={() => onOpenChange(false)}
                    >
                        {cancelText}
                    </Button>
                    <Button variant={variant} onClick={onConfirm}>
                        {confirmText}
                    </Button>
                </DialogFooter>
            </DialogContent>
        </Dialog>
    );
};
```

### Search Component

```tsx
// resources/js/components/search/search-bar.tsx
import { type FC, useState } from 'react';
import { Input } from '@/components/ui/input';
import { Search, X } from 'lucide-react';
import { Button } from '@/components/ui/button';

interface SearchBarProps {
    placeholder?: string;
    onSearch: (query: string) => void;
    debounceMs?: number;
}

export const SearchBar: FC<SearchBarProps> = ({
    placeholder = 'Buscar...',
    onSearch,
    debounceMs = 300,
}) => {
    const [value, setValue] = useState('');

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const newValue = e.target.value;
        setValue(newValue);
        // Implementar debounce aquí
        onSearch(newValue);
    };

    const handleClear = () => {
        setValue('');
        onSearch('');
    };

    return (
        <div className="relative">
            <Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
            <Input
                type="text"
                placeholder={placeholder}
                value={value}
                onChange={handleChange}
                className="pr-10 pl-10"
            />
            {value && (
                <Button
                    variant="ghost"
                    size="sm"
                    className="absolute top-1/2 right-1 h-7 w-7 -translate-y-1/2 p-0"
                    onClick={handleClear}
                >
                    <X className="h-4 w-4" />
                </Button>
            )}
        </div>
    );
};
```

## Convenciones

1. **Archivo**: kebab-case (`data-table.tsx`, `confirm-modal.tsx`)
2. **Componente**: PascalCase sin sufijo (`DataTable`, `ConfirmModal`)
3. **Props Interface**: `{Nombre}Props`
4. **Usar componentes UI de shadcn** cuando sea posible
5. **Tipar todas las props** explícitamente
