npm package: @nerdlat/preview
Description: Un paquete avanzado para previsualizar contenido con React - Componentes configurables y diseños predefinidos
Author: NerdLat
Latest version: 0.2.8

# @nerdlat/preview

<div align="center">
  <h3>🚀 Librería avanzada para previsualizar contenido HTML con React</h3>
  <p>Componentes configurables, diseños predefinidos y herramientas de desarrollo</p>
  
  [![npm version](https://badge.fury.io/js/%40nerdlat%2Fpreview.svg)](https://badge.fury.io/js/%40nerdlat%2Fpreview)
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
</div>

## ✨ Características

- 🎨 **Pantallas de carga personalizables** con diseños predefinidos
- 🔧 **Barra de herramientas configurable** con iconos de react-icons
- 📱 **Vista responsive** con dispositivos predefinidos
- 🌓 **Soporte para temas** (claro/oscuro)
- 🔗 **Integración de redes sociales** en pantallas de carga
- 📦 **Exportación de archivos** a ZIP con metadatos
- ✏️ **Editor de código integrado** con resaltado de sintaxis
- 📝 **Prop \`code\` editable** para escribir lógica en tiempo real
- 📸 **Capturas de pantalla** de la vista previa
- ⚡ **Compatible con React 16.8+** y todas las versiones de ReactDOM
- 🎯 **TypeScript** completamente tipado
- 🖼️ **Modo fullscreen** mejorado

## 📦 Instalación

```bash
npm install @nerdlat/preview
```

```bash
yarn add @nerdlat/preview
```

```bash
pnpm add @nerdlat/preview
```

## 📂 Proyecto de ejemplo (Vite + React)

En la carpeta `example` encontrarás un pequeño proyecto creado con **Vite** que
importa la librería desde este repositorio. Puedes probarlo ejecutando:

```bash
cd example
pnpm install
pnpm run dev
```

## 🚀 Uso Básico

```tsx
import { Preview } from '@nerdlat/preview';

function App() {
  return (
    <Preview
      html="<h1>Hola Mundo</h1>"
      route="/"
    />
  );
}
```

## 🎨 Ejemplos Avanzados

### Con Pantalla de Carga Personalizada

```tsx
import { Preview, LOADING_PRESETS } from '@nerdlat/preview';

function App() {
  return (
    <Preview
      host="http://localhost:3000"
      loading={{
        enabled: true,
        design: LOADING_PRESETS.gradient,
        title: "Mi Aplicación",
        subtitle: "Cargando contenido...",
        socialLinks: [
          { name: 'github', url: 'https://github.com/mi-usuario' },
          { name: 'twitter', url: 'https://twitter.com/mi-usuario' },
        ]
      }}
    />
  );
}
```

### Con Barra de Herramientas Personalizada

```tsx
import { Preview } from '@nerdlat/preview';
import { MdDownload, MdSettings } from 'react-icons/md';

function App() {
  return (
    <Preview
      host="http://localhost:3000"
      theme="dark"
      toolbar={{
        reload: { enabled: true, label: 'Recargar' },
        external: { enabled: true, label: 'Abrir' },
        responsive: { enabled: true },
        fullscreen: { enabled: true, label: 'Pantalla Completa' },
        customButtons: [
          {
            label: 'Exportar',
            onClick: () => handleExport(),
            variant: 'primary'
          }
        ]
      }}
      
      customDevices={[
        { name: 'Mi Dispositivo', width: 414, height: 896 }
      ]}
      
      onRouteChange={(route) => console.log('Nueva ruta:', route)}
      onFullscreenChange={(isFullscreen) => console.log('Fullscreen:', isFullscreen)}
      onLoad={() => console.log('Contenido cargado')}
      onError={(error) => console.error('Error:', error)}
    />
  );
}
```

## 📖 API Reference

### PreviewProps

| Prop | Tipo | Descripción | Default |
|------|------|-------------|---------|
| `host` | `string` | URL del servidor a previsualizar. Si se omite se usa `html` | `-` |
| `route` | `string` | Ruta inicial | `'/'` |
| `html` | `string` | HTML directo (ignora host si se usa) | - |
| `code` | `string` | Código React/TSX editable en el sandbox | - |
| `codeFiles` | `Record<string, string>` | Archivos de código para el editor | - |
| `loading` | `LoadingConfig` | Configuración de pantalla de carga | - |
| `toolbar` | `PreviewToolbar` | Configuración de barra de herramientas | `{}` |
| `theme` | `'light' \| 'dark' \| 'auto'` | Tema de la interfaz | `'light'` |
| `customDevices` | `Device[]` | Dispositivos adicionales | `[]` |
| `className` | `string` | Clase CSS del contenedor | - |
| `style` | `CSSProperties` | Estilos del contenedor | - |
| `onScreenshot` | `(blob: Blob) => void` | Callback al tomar una captura | - |

### LoadingConfig

```tsx
interface LoadingConfig {
  enabled?: boolean;
  design?: LoadingDesign;
  logo?: string;
  title?: string;
  subtitle?: string;
  description?: string;
  socialLinks?: SocialLink[];
  customContent?: ReactNode;
  duration?: number; // tiempo mínimo en ms
}
```

### LoadingDesign

```tsx
interface LoadingDesign {
  type: 'minimal' | 'modern' | 'gradient' | 'animated' | 'custom';
  background?: string;
  primaryColor?: string;
  secondaryColor?: string;
  animation?: 'fade' | 'pulse' | 'bounce' | 'spin' | 'slide';
}
```

### PreviewToolbar

```tsx
interface PreviewToolbar {
  reload?: {
    enabled?: boolean;
    icon?: IconType;
    label?: string;
  };
  external?: {
    enabled?: boolean;
    icon?: IconType;
    label?: string;
  };
  responsive?: {
    enabled?: boolean;
    icon?: IconType;
    label?: string;
  };
  fullscreen?: {
    enabled?: boolean;
    icon?: IconType;
    label?: string;
  };
  screenshot?: {
    enabled?: boolean;
    icon?: IconType;
    label?: string;
  };
  editor?: {
    enabled?: boolean;
    icon?: IconType;
    label?: string;
  };
  customButtons?: ToolbarButton[];
}
```

## 🎨 Presets Disponibles

### Diseños de Carga

```tsx
import { LOADING_PRESETS } from '@nerdlat/preview';

// Disponibles:
LOADING_PRESETS.minimal    // Diseño minimalista
LOADING_PRESETS.modern     // Diseño moderno con gradiente
LOADING_PRESETS.gradient   // Gradiente personalizable
LOADING_PRESETS.animated   // Con animaciones
```

### Dispositivos Predefinidos

```tsx
import { DEVICE_PRESETS } from '@nerdlat/preview';

// Disponibles:
DEVICE_PRESETS.mobile     // iPhone, Samsung, Pixel, etc.
DEVICE_PRESETS.tablet     // iPad, Galaxy Tab, Surface, etc.  
DEVICE_PRESETS.desktop    // Resoluciones de escritorio
```

### Configuraciones de Ejemplo

```tsx
import { EXAMPLE_CONFIGS } from '@nerdlat/preview';

// Disponibles:
EXAMPLE_CONFIGS.basic        // Configuración básica
EXAMPLE_CONFIGS.responsive   // Con modo responsive
EXAMPLE_CONFIGS.withLoading  // Con pantalla de carga
EXAMPLE_CONFIGS.advanced     // Configuración completa
```

## 🔧 Utilidades

### Exportación de Archivos

```tsx
import { exportFiles, downloadBlob, exportAndDownload } from '@nerdlat/preview';

// Exportar archivos a ZIP
const files = {
  'index.html': '<html>...</html>',
  'styles.css': 'body { margin: 0; }',
  'script.js': 'console.log("Hello World");'
};

// Generar ZIP
const blob = await exportFiles(files, {
  folderName: 'mi-proyecto',
  includeMetadata: true,
  compression: 'best'
});

// Descargar automáticamente
await exportAndDownload(files, 'proyecto.zip');
```

### Utilidades de Sandbox

```tsx
import {
  createSandbox,
  compileCode,
  runCodeInSandbox,
  destroySandbox
} from '@nerdlat/preview';

// Crear un iframe de prueba
const iframe = createSandbox('<div id="root"></div>');

// Compilar y ejecutar código React dentro del iframe
const code = `const App = () => <h1>Hola</h1>;\nReactDOM.createRoot(document.getElementById('root')).render(<App />);`;
runCodeInSandbox(code, iframe);

// Eliminar el sandbox cuando termine la prueba
destroySandbox(iframe);
```

La función `compileCode` utiliza [Sucrase](https://github.com/alangpierce/sucrase) para transformar TSX/JSX a JavaScript de forma ligera y rápida.

## 🎯 Eventos y Callbacks

```tsx
<Preview
  onRouteChange={(route) => {
    // Se ejecuta cuando cambia la ruta
    console.log('Nueva ruta:', route);
  }}
  
  onFullscreenChange={(isFullscreen) => {
    // Se ejecuta al entrar/salir de fullscreen
    console.log('Fullscreen activo:', isFullscreen);
  }}
  
  onLoad={() => {
    // Se ejecuta cuando se carga el contenido
    console.log('Contenido cargado exitosamente');
  }}
  
  onError={(error) => {
    // Se ejecuta cuando hay un error
    console.error('Error en preview:', error);
  }}
/>
```

## 🌐 Redes Sociales Soportadas

Las siguientes redes sociales tienen iconos predefinidos:

- `github` - GitHub
- `twitter` - Twitter/X  
- `linkedin` - LinkedIn
- `instagram` - Instagram
- `facebook` - Facebook
- `youtube` - YouTube
- `discord` - Discord
- `telegram` - Telegram

```tsx
// Uso automático de iconos
socialLinks: [
  { name: 'github', url: 'https://github.com/usuario' },
  { name: 'twitter', url: 'https://twitter.com/usuario' }
]

// Icono personalizado
import { FaSlack } from 'react-icons/fa';

socialLinks: [
  { 
    name: 'slack', 
    url: 'https://slack.com/usuario',
    icon: FaSlack 
  }
]
```

## 🔄 Migración desde v0.1.x

### Props Deprecados (aún funcionan)

```tsx
// ❌ Versión anterior
<Preview
  showLoading={true}
  logo="/logo.png"
  text="Cargando..."
  reloadTitle="Recargar"
  responsive={true}
  fullscreen={true}
  openPreviewTitle="Abrir"
/>

// ✅ Nueva versión
<Preview
  loading={{
    enabled: true,
    logo: "/logo.png",
    title: "Cargando..."
  }}
  toolbar={{
    reload: { enabled: true, label: "Recargar" },
    external: { enabled: true, label: "Abrir" },
    responsive: { enabled: true },
    fullscreen: { enabled: true }
  }}
/>
```

## 🛠️ Desarrollo

```bash
# Instalar dependencias
npm install

# Desarrollo
npm run dev

# Build
npm run build

# Generar tipos
npm run build:types
```

## 📄 Licencia

MIT © [NerdLat](https://github.com/nerdlat)

## 🤝 Contribuir

Las contribuciones son bienvenidas. Por favor:

1. Fork el proyecto
2. Crea una rama para tu feature (`git checkout -b feature/nueva-funcionalidad`)
3. Commit tus cambios (`git commit -am 'Añadir nueva funcionalidad'`)
4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
5. Abre un Pull Request

## 📞 Soporte

- 🐛 [Reportar bugs](https://github.com/nerdlat/preview/issues)
- 💡 [Solicitar features](https://github.com/nerdlat/preview/issues)
- 📖 [Documentación](https://github.com/nerdlat/preview#readme)
- 💬 [Discusiones](https://github.com/nerdlat/preview/discussions)

---

<div align="center">
  <p>Hecho con ❤️ por <a href="https://github.com/nerd-lat">NerdLat</a></p>
</div> 

```js
{ enabled: true },
        customButtons: [
          {
            icon: MdDownload,
            label: 'Descargar',
            onClick: () => console.log('Descargando...'),
            variant: 'primary'
          },
          {
            icon: MdSettings,
            label: 'Configurar',
            onClick: () => console.log('Configurando...'),
          }
        ]
      }}
    />
  );
}
```

### EJEMPLOS DE USO


```javascript
import React, { useState } from 'react';
import { 
  Preview, 
  LOADING_PRESETS, 
  EXAMPLE_CONFIGS,
  exportAndDownload,
  type PreviewProps 
} from '@nerdlat/preview';
import { MdDownload, MdSettings, MdCode } from 'react-icons/md';

// Ejemplo 1: Uso básico
export function BasicExample() {
  return (
    <div style={{ height: '600px', width: '100%' }}>
      <Preview
        host="http://localhost:3000"
        route="/"
      />
    </div>
  );
}

```
```javascript

// Ejemplo 2: Con pantalla de carga moderna
export function LoadingExample() {
  return (
    <div style={{ height: '600px', width: '100%' }}>
      <Preview
        host="http://localhost:3000"
        loading={{
          enabled: true,
          design: LOADING_PRESETS.modern,
          title: "Mi App Increíble",
          subtitle: "Cargando la experiencia...",
          socialLinks: [
            { name: 'github', url: 'https://github.com/mi-usuario' },
            { name: 'twitter', url: 'https://twitter.com/mi-usuario' },
            { name: 'linkedin', url: 'https://linkedin.com/in/mi-usuario' },
          ],
          duration: 2000
        }}
        toolbar={{
          reload: { enabled: true, label: 'Actualizar' },
          external: { enabled: true, label: 'Nueva Pestaña' },
          responsive: { enabled: true },
          fullscreen: { enabled: true },
        }}
      />
    </div>
  );
}
```
```javascript

// Ejemplo 3: Tema oscuro con botones personalizados
export function DarkThemeExample() {
  const handleExport = async () => {
    const files = {
      'index.html': '<!DOCTYPE html><html><head><title>Mi App</title></head><body><h1>Hola Mundo</h1></body></html>',
      'styles.css': 'body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }',
      'script.js': 'console.log("Aplicación iniciada");',
    };
    
    await exportAndDownload(files, 'mi-proyecto.zip', {
      folderName: 'mi-proyecto',
      includeMetadata: true,
      compression: 'best'
    });
  };

  return (
    <div style={{ height: '600px', width: '100%' }}>
      <Preview
        host="http://localhost:3000"
        theme="dark"
        loading={{
          enabled: true,
          design: {
            type: 'gradient',
            primaryColor: '#3b82f6',
            secondaryColor: '#8b5cf6',
            animation: 'pulse'
          },
          title: "Modo Oscuro",
          subtitle: "Experiencia nocturna",
          description: "Interfaz optimizada para ambientes de poca luz"
        }}
        toolbar={{
          reload: { enabled: true, label: 'Recargar' },
          external: { enabled: true, label: 'Abrir' },
          responsive: { enabled: true, label: 'Dispositivos' },
          fullscreen: { enabled: true, label: 'Pantalla Completa' },
          customButtons: [
            {
              icon: MdDownload,
              label: 'Exportar',
              onClick: handleExport,
              variant: 'primary'
            },
            {
              icon: MdSettings,
              label: 'Configurar',
              onClick: () => alert('Configuraciones'),
              variant: 'secondary'
            },
            {
              icon: MdCode,
              label: 'Ver Código',
              onClick: () => console.log('Mostrando código...'),
            }
          ]
        }}
      />
    </div>
  );
}
```
```js


// Ejemplo 4: Responsive con dispositivos personalizados
export function ResponsiveExample() {
  const [currentRoute, setCurrentRoute] = useState('/dashboard');

  const customDevices = [
    { name: 'iPhone 15 Pro Max', width: 430, height: 932 },
    { name: 'Samsung Galaxy Fold', width: 280, height: 653 },
    { name: 'iPad Pro 11"', width: 834, height: 1194 },
    { name: 'MacBook Pro 14"', width: 1512, height: 982 },
    { name: 'Mi Dispositivo Custom', width: 375, height: 812 },
  ];

  return (
    <div style={{ height: '700px', width: '100%' }}>
      <Preview
        host="http://localhost:3000"
        route={currentRoute}
        customDevices={customDevices}
        loading={{
          enabled: true,
          design: LOADING_PRESETS.animated,
          title: "Vista Responsive",
          subtitle: "Probando en múltiples dispositivos",
          logo: "/logo.png"
        }}
        toolbar={{
          reload: { enabled: true },
          external: { enabled: true },
          responsive: { enabled: true },
          fullscreen: { enabled: true },
        }}
        onRouteChange={(route) => {
          setCurrentRoute(route);
          console.log('Ruta cambiada a:', route);
        }}
        onFullscreenChange={(isFullscreen) => {
          console.log('Fullscreen:', isFullscreen);
        }}
        onLoad={() => {
          console.log('Contenido cargado en ruta:', currentRoute);
        }}
        onError={(error) => {
          console.error('Error en preview:', error);
        }}
      />
    </div>
  );
}

```
```js

// Ejemplo 5: HTML directo (sin servidor)
export function HTMLDirectExample() {
  const htmlContent = `
    <!DOCTYPE html>
    <html lang="es">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Contenido Directo</title>
        <style>
            body {
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                margin: 0;
                padding: 40px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                min-height: 100vh;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .container {
                text-align: center;
                background: rgba(255, 255, 255, 0.1);
                padding: 60px;
                border-radius: 20px;
                backdrop-filter: blur(10px);
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
            }
            h1 {
                font-size: 3rem;
                margin-bottom: 1rem;
                text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
            }
            p {
                font-size: 1.25rem;
                line-height: 1.6;
                margin-bottom: 2rem;
            }
            .button {
                display: inline-block;
                padding: 15px 30px;
                background: white;
                color: #667eea;
                text-decoration: none;
                border-radius: 50px;
                font-weight: bold;
                transition: transform 0.2s ease;
            }
            .button:hover {
                transform: translateY(-2px);
            }
        </style>
    </head>
    <body>
        <div class="container">
            <h1>🚀 NerdLat Preview</h1>
            <p>
                Este es un ejemplo de contenido HTML directo.<br>
                No requiere servidor externo para funcionar.
            </p>
            <a href="#" class="button" onclick="alert('¡Funciona!')">
                Hacer Click
            </a>
        </div>
    </body>
    </html>
  `;

  return (
    <div style={{ height: '600px', width: '100%' }}>
      <Preview
        html={htmlContent}
        loading={{
          enabled: true,
          design: LOADING_PRESETS.minimal,
          title: "Cargando Contenido",
          subtitle: "HTML directo",
        }}
        toolbar={{
          reload: { enabled: true },
          responsive: { enabled: true },
          fullscreen: { enabled: true },
        }}
      />
    </div>
  );
}

```
```js

// Ejemplo 6: Configuración desde presets
export function PresetExample() {
  return (
    <div style={{ height: '600px', width: '100%' }}>
      <Preview
        {...EXAMPLE_CONFIGS.advanced}
        host="http://localhost:3000"
        route="/api/docs"
      />
    </div>
  );
}

// Componente principal que muestra todos los ejemplos
export default function EjemplosCompletos() {
  const [selectedExample, setSelectedExample] = useState('basic');

  const examples = {
    basic: { component: BasicExample, title: 'Uso Básico' },
    loading: { component: LoadingExample, title: 'Con Pantalla de Carga' },
    dark: { component: DarkThemeExample, title: 'Tema Oscuro' },
    responsive: { component: ResponsiveExample, title: 'Responsive' },
    html: { component: HTMLDirectExample, title: 'HTML Directo' },
    preset: { component: PresetExample, title: 'Desde Presets' },
  };

  const CurrentExample = examples[selectedExample as keyof typeof examples].component;

  return (
    <div style={{ padding: '20px' }}>
      <h1>🚀 @nerdlat/preview - Ejemplos</h1>
      
      {/* Selector de ejemplos */}
      <div style={{ marginBottom: '20px' }}>
        <label style={{ marginRight: '10px' }}>Seleccionar ejemplo:</label>
        <select 
          value={selectedExample} 
          onChange={(e) => setSelectedExample(e.target.value)}
          style={{ 
            padding: '8px 12px', 
            borderRadius: '4px', 
            border: '1px solid #ccc' 
          }}
        >
          {Object.entries(examples).map(([key, { title }]) => (
            <option key={key} value={key}>{title}</option>
          ))}
        </select>
      </div>

      {/* Ejemplo actual */}
      <div style={{ 
        border: '1px solid #e5e7eb', 
        borderRadius: '8px', 
        overflow: 'hidden' 
      }}>
        <CurrentExample />
      </div>

      {/* Información del ejemplo */}
      <div style={{ 
        marginTop: '20px', 
        padding: '16px', 
        background: '#f8fafc', 
        borderRadius: '8px' 
      }}>
        <h3>📋 {examples[selectedExample as keyof typeof examples].title}</h3>
        <p>
          Este ejemplo muestra diferentes características de la librería.
          Revisa el código fuente para ver cómo implementar cada funcionalidad.
        </p>
      </div>
    </div>
  );
}
```