0.2.8 • Published 11 months ago

@nerdlat/preview v0.2.8

Weekly downloads
-
License
MIT
Repository
github
Last release
11 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

11 months ago

0.2.7

11 months ago

0.2.6

11 months ago

0.2.4

11 months ago

0.2.2

11 months ago

0.2.1

11 months ago

0.1.6

11 months ago

0.1.5

11 months ago

0.1.4

11 months ago

0.1.2

11 months ago

0.1.1

11 months ago