Este artículo proporciona una guía detallada para integrar el sistema de diseño 98.css, que recrea fielmente interfaces de usuario antiguas, con frameworks frontend modernos como React y Vue.js, además de cubrir las mejores prácticas con TypeScript. Se exploran estrategias para la encapsulación de componentes, la gestión del estado, la adaptación responsiva y la optimización del rendimiento, ofreciendo a los desarrolladores un enfoque integral para crear aplicaciones con estética retro usando herramientas contemporáneas.
Integración Práctica en Proyectos React
Integrar 98.css en un proyecto de React permite combinar la estética de Windows 98 con la arquitectura basada en componentes. La isntalación se realiza mediante npm:
npm install 98.css
Luego, se importa la hoja de estilos en el archivo de entrada de la aplicación, por ejemplo, index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import '98.css';
import App from './App';
const raiz = ReactDOM.createRoot(document.getElementById('root'));
raiz.render(
<react.strictmode>
<app></app>
</react.strictmode>
);
Encapsulación de Componentes
Para aprovechar la reutilización en React, se pueden crear componentes específicos que utilicen las clases CSS de 98.css. Un componente Ventana podría estructurarse así:
// componentes/Ventana.jsx
import React from 'react';
const Ventana = ({ titulo, contenido, claseAdicional = '' }) => {
return (
<div classname="{`ventana" margin:="" style="{{" width:="">
<div classname="barra-titulo">
<div classname="texto-barra-titulo">{titulo}</div>
<div classname="controles-barra-titulo">
<button aria-label="Minimizar"></button>
<button aria-label="Maximizar"></button>
<button aria-label="Cerrar"></button>
</div>
</div>
<div classname="cuerpo-ventana">
{contenido}
</div>
</div>
);
};
export default Ventana;
Manejo de Formularios
Los controles de formulario de 98.css se integran sin problemas con el manejo de estado de React:
// componentes/FormularioRetro.jsx
import React, { useState } from 'react';
const FormularioRetro = () => {
const [datos, setDatos] = useState({
usuario: '',
clave: '',
recordar: false
});
const enviar = (e) => {
e.preventDefault();
console.log('Datos enviados:', datos);
};
const actualizar = (e) => {
const { nombre, valor, tipo, checked } = e.target;
setDatos(prev => ({
...prev,
[nombre]: tipo === 'checkbox' ? checked : valor
}));
};
return (
<form onsubmit="{enviar}">
<div marginbottom:="" style="{{">
<label htmlfor="usuario">Usuario:</label>
<input id="usuario" nombre="usuario" onchange="{actualizar}" style="{{" type="text" valor="{datos.usuario}" width:=""></input>
</div>
<div marginbottom:="" style="{{">
<label htmlfor="clave">Contraseña:</label>
<input id="clave" nombre="clave" onchange="{actualizar}" style="{{" type="password" valor="{datos.clave}" width:=""></input>
</div>
<div marginbottom:="" style="{{">
<input checked="{datos.recordar}" id="recordar" nombre="recordar" onchange="{actualizar}" type="checkbox"></input>
<label htmlfor="recordar">Recordarme</label>
</div>
<button classname="default" type="submit">Iniciar sesión</button>
</form>
);
};
export default FormularioRetro;
Sincronización Estado-Estilos
El estado de React puede controlar aspectos visuales de los componentes, como la retroalimentación al hacer clic:
// componentes/BotonInteractivo.jsx
import React, { useState } from 'react';
const BotonInteractivo = () => {
const [activo, setActivo] = useState(false);
const [clics, setClics] = useState(0);
const manejarClic = () => {
setActivo(true);
setClics(prev => prev + 1);
setTimeout(() => setActivo(false), 150);
};
return (
<div classname="ventana">
<div classname="barra-titulo">
<div classname="texto-barra-titulo">Demostración Interactiva</div>
</div>
<div classname="cuerpo-ventana">
<p>Clics: {clics}</p>
<button :="" activo="" classname="{activo" ease="" onclick="{manejarClic}" style="{{" transform:="" transition:="">
¡Haz Clic!
</button>
</div>
</div>
);
};
export default BotonInteractivo;
Diseño Adaptativo
Se puede crear un hook personalizado para gestionar el tamaño de la ventana y adaptar la presentación:
// hooks/useDimensionesVentana.js
import { useState, useEffect } from 'react';
const useDimensionesVentana = () => {
const [dimensiones, setDimensiones] = useState({
ancho: window.innerWidth,
alto: window.innerHeight,
});
useEffect(() => {
const redimensionar = () => {
setDimensiones({
ancho: window.innerWidth,
alto: window.innerHeight,
});
};
window.addEventListener('resize', redimensionar);
return () => window.removeEventListener('resize', redimensionar);
}, []);
return dimensiones;
};
// componentes/VentanaAdaptativa.jsx
import React from 'react';
import useDimensionesVentana from '../hooks/useDimensionesVentana';
const VentanaAdaptativa = ({ contenido }) => {
const { ancho } = useDimensionesVentana();
const esMovil = ancho < 768;
return (
<div :="" classname="ventana" esmovil="" margin:="" maxwidth:="" style="{{" width:="">
<div classname="barra-titulo">
<div classname="texto-barra-titulo">
{esMovil ? 'Vista Móvil' : 'Vista Escritorio'}
</div>
</div>
<div classname="cuerpo-ventana">
{contenido}
</div>
</div>
);
};
export default VentanaAdaptativa;
Estilo Retro en Aplicaciones Vue.js
Vue.js ofrece un ecosistema reactivo y modular. Para integrar 98.css, se pueden mapear sus clases CSS a componentes de Vue:
<template>
<div class="ventana">
<div class="barra-titulo">
<div class="texto-barra-titulo">{{ titulo }}</div>
<div class="controles-barra-titulo">
<button aria-label="Minimizar"></button>
<button aria-label="Maximizar"></button>
<button aria-label="Cerrar"></button>
</div>
</div>
<div class="cuerpo-ventana">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
titulo: String,
activo: Boolean
},
computed: {
claseBarraTitulo() {
return {
'barra-titulo': true,
'inactivo': !this.activo
}
}
}
}
</script>
La personalización de temas se logra modificando variables CSS en el nivel raíz:
<script>
export default {
mounted() {
document.documentElement.style.setProperty('--azul-dialogo', '#0066cc');
document.documentElement.style.setProperty('--superficie', '#d4d0c8');
}
}
</script>
<style>
:root {
--primario-personalizado: #008080;
--acento-personalizado: #ff6b35;
}
</style>
Las transiciones de Vue pueden aplicarse a los componentes de 98.css para una experiencia más pulida:
<template>
<transition name="desvanecer-ventana">
<div v-if="mostrar" class="ventana" :style="{ zIndex: capa }">
<!-- Contenido -->
</div>
</transition>
</template>
<style>
.desvanecer-ventana-enter-active,
.desvanecer-ventana-leave-active {
transition: opacity 0.3s, transform 0.3s;
}
.desvanecer-ventana-enter-from,
.desvanecer-ventana-leave-to {
opacity: 0;
transform: scale(0.9);
}
</style>
Definiciones de Tipo y Enacpsulación con TypeScript
TypeScript proporciona seguridad de tipos para los componentes. Se pueden definir interfaces para las propiedades de los componentes y las clases CSS de 98.css:
// tipos/98css.d.ts
interface ClasesVentana {
ventana: string;
'barra-titulo': string;
'texto-barra-titulo': string;
'cuerpo-ventana': string;
inactivo: string;
}
interface ClasesBoton {
boton: string;
default: string;
activo: string;
}
// components/Ventana.tsx
import React from 'react';
interface PropsVentana {
titulo: string;
ancho?: number;
alto?: number;
inactivo?: boolean;
contenido: React.ReactNode;
alCerrar?: () => void;
}
export const Ventana: React.FC<PropsVentana> = ({
titulo,
ancho,
alto,
inactivo = false,
contenido,
alCerrar
}) => {
const estilo: React.CSSProperties = {
width: ancho ? `${ancho}px` : undefined,
height: alto ? `${alto}px` : undefined,
};
return (
<div :="" classname="{`ventana" style="{estilo}">
<div classname="barra-titulo">
<div classname="texto-barra-titulo">{titulo}</div>
<div classname="controles-barra-titulo">
{alCerrar && (
<button aria-label="Cerrar" onclick="{alCerrar}"></button>
)}
</div>
</div>
<div classname="cuerpo-ventana">
{contenido}
</div>
</div>
);
};
Optimización del Rendimiento
Para aplicaciones de gran escala, se pueden aplicar técnicas como la carga diferida de la hoja de estilos:
// Carga diferida del CSS
const cargar98CSS = async () => {
if (typeof window !== 'undefined') {
await import('98.css');
}
};
// Uso con React.lazy y Suspense
import React, { Suspense } from 'react';
const AppRetroCargaDiferida = () => {
const [cargado, setCargado] = React.useState(false);
React.useEffect(() => {
cargar98CSS().then(() => setCargado(true));
}, []);
if (!cargado) return <div>Cargando estilos retro...</div>;
return (
<Suspense fallback={<div>Cargando componentes...</div>}>
{/* Componentes que usan 98.css */}
</Suspense>
);
};
Adicionalmente, se recomienda utilizar React.memo para componentes puros y realizar una extracción selectiva de estilos para reducir el tamaño del paquete final, aplicando técnicas de tree-shaking cuando sea posible.