npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nerdlat/preview

v0.2.8

Published

Un paquete avanzado para previsualizar contenido con React - Componentes configurables y diseños predefinidos

Readme

@nerdlat/preview

npm version License: MIT

✨ 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

| 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

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