0.2.8 • Published 5 months ago

@nerdlat/preview v0.2.8

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

@nerdlat/preview

✨ 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

npm install @nerdlat/preview
yarn add @nerdlat/preview
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:

cd example
pnpm install
pnpm run dev

🚀 Uso Básico

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

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

🎨 Ejemplos Avanzados

Con Pantalla de Carga Personalizada

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

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

PropTipoDescripciónDefault
hoststringURL del servidor a previsualizar. Si se omite se usa html-
routestringRuta inicial'/'
htmlstringHTML directo (ignora host si se usa)-
codestringCódigo React/TSX editable en el sandbox-
codeFilesRecord<string, string>Archivos de código para el editor-
loadingLoadingConfigConfiguración de pantalla de carga-
toolbarPreviewToolbarConfiguración de barra de herramientas{}
theme'light' \| 'dark' \| 'auto'Tema de la interfaz'light'
customDevicesDevice[]Dispositivos adicionales[]
classNamestringClase CSS del contenedor-
styleCSSPropertiesEstilos del contenedor-
onScreenshot(blob: Blob) => voidCallback al tomar una captura-

LoadingConfig

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

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

PreviewToolbar

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

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

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

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

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

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 para transformar TSX/JSX a JavaScript de forma ligera y rápida.

🎯 Eventos y Callbacks

<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
// 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)

// ❌ 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

# Instalar dependencias
npm install

# Desarrollo
npm run dev

# Build
npm run build

# Generar tipos
npm run build:types

📄 Licencia

MIT © 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


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

EJEMPLOS DE USO

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>
  );
}
// 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>
  );
}
// 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>
  );
}
// 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>
  );
}
// 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>
  );
}
// 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>
  );
}
0.2.8

5 months ago

0.2.7

5 months ago

0.2.6

5 months ago

0.2.4

5 months ago

0.2.2

5 months ago

0.2.1

5 months ago

0.1.6

5 months ago

0.1.5

5 months ago

0.1.4

5 months ago

0.1.2

5 months ago

0.1.1

5 months ago