0.2.8 • Published 6 months ago
@nerdlat/preview v0.2.8
@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/previewyarn add @nerdlat/previewpnpm 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 animacionesDispositivos 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 escritorioConfiguraciones 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- GitHubtwitter- Twitter/Xlinkedin- LinkedIninstagram- Instagramfacebook- Facebookyoutube- YouTubediscord- Discordtelegram- 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:
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/nueva-funcionalidad) - Commit tus cambios (
git commit -am 'Añadir nueva funcionalidad') - Push a la rama (
git push origin feature/nueva-funcionalidad) - 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>
);
}