Saltar al contenido principal

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

ComponenteDescripción
ButtonBotón estándar con soporte de variantes
CardTarjeta contenedora
CardHeaderSección de encabezado de la tarjeta
CardTitleTexto del título de la tarjeta
CardContentContenido principal de la tarjeta
CardDescriptionTexto descriptivo de la tarjeta
SwitchInterruptor de activación
SliderControl deslizante de rango
LabelEtiqueta de formulario
InputCampo de texto
BadgeInsignia de estado
ProgressBarra de progreso
TooltipContenedor de tooltip
TooltipProviderProveedor de contexto para tooltips
TooltipTriggerElemento activador del tooltip
TooltipContentContenido emergente del tooltip
SubwayButtonBotón con estilo del juego
MainMenuButtonBotó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

ComponenteUso
ResponsiveContainerContenedor con tamaño automático (úsalo alrededor de cada gráfico)
BarChart, BarGráficos de barras
LineChart, LineGráficos de líneas
AreaChart, AreaGráficos de área
PieChart, Pie, CellGráficos de pastel/dona
RadarChart, RadarGráficos radar
ComposedChartTipos de gráficos combinados
XAxis, YAxisEjes
CartesianGridLíneas de cuadrícula
TooltipTooltips al pasar el cursor
LegendLeyenda 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 atenuado
  • bg-card — fondo de tarjeta
  • border — borde con estilo del tema
  • rounded-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,
});