Componentes de React
Los mods de Subway Builder pueden renderizar UI personalizada usando React. El juego proporciona React en tiempo de ejecución — tu mod no necesita incluirlo en el bundle.
Cómo funciona el React Shim
La plantilla incluye un React shim en src/types/react.ts que obtiene React desde la API del juego:
const React = window.SubwayBuilderAPI.utils.React;
export default React;
export const { useState, useEffect, useCallback, useMemo, useRef } = React;
Vite está configurado para redirigir todos los imports de react a este shim, así que puedes
escribir React estándar:
import { useState, useEffect } from 'react';
Esto simplemente funciona — el import se redirige al shim en tiempo de compilación y, en tiempo de ejecución, utiliza la instancia de React del juego.
Uso de Componentes de UI del Juego
El juego expone un conjunto de componentes de UI con estilos. Accede a ellos desde la API:
const api = window.SubwayBuilderAPI;
// Haz cast a any ya que los componentes funcionan en tiempo de ejecución pero no tienen tipos estrictos para sus props
const { Button, Card, CardHeader, CardTitle, CardContent } = api.utils.components as Record<
string,
React.ComponentType<any>
>;
Componentes Disponibles
| Componente | Descripción |
|---|---|
Button | Botón estándar con soporte de variantes |
Card | Tarjeta contenedora |
CardHeader | Sección de encabezado de la tarjeta |
CardTitle | Texto del título de la tarjeta |
CardContent | Contenido principal de la tarjeta |
CardDescription | Texto descriptivo de la tarjeta |
Switch | Interruptor de activación |
Slider | Control deslizante de rango |
Label | Etiqueta de formulario |
Input | Campo de texto |
Badge | Insignia de estado |
Progress | Barra de progreso |
Tooltip | Contenedor de tooltip |
TooltipProvider | Proveedor de contexto para tooltips |
TooltipTrigger | Elemento activador del tooltip |
TooltipContent | Contenido emergente del tooltip |
SubwayButton | Botón con estilo del juego |
MainMenuButton | Botón con estilo del menú principal |
Ejemplo de Button
const { Button } = api.utils.components as Record<string, React.ComponentType<any>>;
function MyPanel() {
return (
<div className="flex flex-col gap-2">
<Button onClick={() => console.log('click')}>Predeterminado</Button>
<Button variant="secondary">Secundario</Button>
<Button variant="destructive">Eliminar</Button>
<Button variant="outline">Contorno</Button>
<Button variant="ghost">Fantasma</Button>
</div>
);
}
Ejemplo de Card
const { Card, CardHeader, CardTitle, CardContent } = api.utils.components as Record<
string,
React.ComponentType<any>
>;
function StatsCard() {
const stats = api.gameState.getRidershipStats();
return (
<Card>
<CardHeader>
<CardTitle>Número de pasajeros</CardTitle>
</CardHeader>
<CardContent>
<p>{stats.totalRidersPerHour} pasajeros/hora</p>
<p>{stats.totalRiders} total</p>
</CardContent>
</Card>
);
}
Uso de Iconos Lucide
El juego proporciona ~5000 iconos de Lucide. Accede a ellos por su nombre en PascalCase:
const icons = api.utils.icons;
function MyPanel() {
const TrainIcon = icons.Train;
const SettingsIcon = icons.Settings;
const MapPinIcon = icons.MapPin;
return (
<div className="flex items-center gap-2">
<TrainIcon className="w-4 h-4" />
<span>Trenes</span>
</div>
);
}
Iconos comunes que podrías usar: Train, MapPin, Settings, DollarSign, BarChart, Route,
Building, Layers, Clock, Play, Pause, Zap, Plus, Minus, Search, Filter,
Download, Save.
Para nombres de iconos usados en addFloatingPanel, addToolbarButton, etc., utiliza directamente
el nombre en PascalCase como string:
api.ui.addFloatingPanel({
id: 'my-panel',
icon: 'Train', // Nombre como string, no el componente
title: 'Mi Panel',
render: MyPanel,
});
Uso de Recharts
El juego incluye Recharts para visualización de datos. Todos los componentes estándar están disponibles:
const { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip } =
api.utils.charts;
function RidershipChart() {
const metrics = api.gameState.getLineMetrics();
const data = metrics.map((m) => ({
name: m.name,
riders: m.ridersPerHour,
}));
return (
<ResponsiveContainer width="100%" height={200}>
<BarChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="riders" fill="#3b82f6" />
</BarChart>
</ResponsiveContainer>
);
}
Componentes de Gráficos Disponibles
| Componente | Uso |
|---|---|
ResponsiveContainer | Contenedor con tamaño automático (úsalo alrededor de cada gráfico) |
BarChart, Bar | Gráficos de barras |
LineChart, Line | Gráficos de líneas |
AreaChart, Area | Gráficos de área |
PieChart, Pie, Cell | Gráficos de pastel/dona |
RadarChart, Radar | Gráficos radar |
ComposedChart | Tipos de gráficos combinados |
XAxis, YAxis | Ejes |
CartesianGrid | Líneas de cuadrícula |
Tooltip | Tooltips al pasar el cursor |
Legend | Leyenda del gráfico |
Estilos con Tailwind
El juego utiliza Tailwind CSS. Puedes usar clases utilitarias de Tailwind directamente en tus componentes:
function MyPanel() {
return (
<div className="flex flex-col gap-3 p-3">
<h3 className="text-lg font-semibold">My Mod</h3>
<p className="text-sm text-muted-foreground">Some description</p>
<div className="grid grid-cols-2 gap-2">
<div className="bg-card rounded-lg p-2">Cell 1</div>
<div className="bg-card rounded-lg p-2">Cell 2</div>
</div>
</div>
);
}
Clases útiles adaptadas al tema:
text-muted-foreground— color de texto atenuadobg-card— fondo de tarjetaborder— borde con estilo del temarounded-lg/rounded-md— radio de borde
Consejos para Floating Panels
Al renderizar dentro de un floating panel (addFloatingPanel), el panel proporciona su propio
contenedor — no necesitas envolver tu componente en una Card:
// Correcto — el panel proporciona el contenedor
function MyPanel() {
return (
<div className="flex flex-col gap-3 p-3">
<p>Contenido aquí</p>
</div>
);
}
// Registrándolo
api.ui.addFloatingPanel({
id: 'my-panel',
title: 'Mi Mod',
icon: 'Puzzle',
render: MyPanel,
});
Registrar Componentes Personalizados en Ubicaciones
Para una UI más avanzada, puedes registrar un componente de React directamente en una ubicación de la interfaz:
function MySettingsSection() {
const [enabled, setEnabled] = useState(true);
return (
<div className="flex items-center justify-between p-2">
<span>Mi Función</span>
<Switch checked={enabled} onCheckedChange={setEnabled} />
</div>
);
}
api.ui.registerComponent('settings-menu', {
id: 'my-settings',
component: MySettingsSection,
});