Rail cards
Columna derecha del inicio (320 px). Listas cortas con “Ver todo”.
Tarjetas compactas para la columna secundaria del inicio. Un título, pocas filas y una salida a la vista completa. El contenido puede ser clima por equipo o próximos eventos de cualquier organización.
Instalación
shadcn
npx shadcn@latest add https://design.moodinary.com/r/rail-card.jsonimport
import { RailCard } from "@moodinary/ui/components/rail-card";
import { EventRow } from "@moodinary/ui/components/rail-row";Ejemplos
Clima por equipo
La fila real de Inicio: punto de ánimo, equipo, nivel, promedio y flecha de tendencia. Cada fila lleva a los pulsos de ese equipo; sin datos muestra un punto gris.
Clima por equipo
Ver analíticas →Muy bajo <1,8 · Bajo <2,6 · Neutral <3,4 · Bien <4,2 · Muy bien
Código
teams.tsx
import { MOOD_NAMES, MoodDot } from "@moodinary/ui/components/mood-dot";
import { RailCard } from "@moodinary/ui/components/rail-card";
/* Igual que TeamClimateRow en apps/pulses/.../inicio-rail.tsx: punto, nombre, nivel, promedio y tendencia. */
/* clampLevel de apps/pulses/src/lib/mood.ts: redondeo al nivel más cercano. */
const clampLevel = (avg: number) =>
Math.max(1, Math.min(5, Math.round(avg))) as 1 | 2 | 3 | 4 | 5;
const teams: { name: string; avg: number | null; delta: number | null }[] = [
{ avg: 3.6, delta: 0.2, name: "4.º A" },
{ avg: 3.3, delta: 0, name: "Administración" },
{ avg: 3.0, delta: -0.1, name: "6.º A" },
{ avg: 2.4, delta: -0.6, name: "5.º B" },
{ avg: null, delta: null, name: "Mantenimiento" },
];
function Trend({ delta }: { delta: number | null }) {
if (delta === null || Math.abs(delta) < 0.1) {
return (
<span aria-hidden className="w-3 text-muted-foreground text-xs">
→
</span>
);
}
return (
<span
className={
delta > 0 ? "w-3 text-success text-xs" : "w-3 text-destructive text-xs"
}
>
<span aria-hidden>{delta > 0 ? "↑" : "↓"}</span>
<span className="sr-only">{delta > 0 ? "sube" : "baja"}</span>
</span>
);
}
export default function RailTeams() {
return (
<RailCard
action={<a href="#analiticas">Ver analíticas →</a>}
className="w-[340px] max-w-full"
title="Clima por equipo"
>
<ul className="m-0 grid list-none gap-2.5 p-0 text-sm">
{teams.map(({ name, avg, delta }) => (
<li key={name}>
<a
className="-mx-1.5 block rounded-[7px] px-1.5 py-0.5 text-ink no-underline outline-none transition-colors hover:bg-bg-soft focus-visible:ring-3 focus-visible:ring-org-ring"
href="#pulsos"
title={`Ver pulsos de ${name}`}
>
<span className="flex items-center gap-2.5">
{avg === null ? (
<span
aria-hidden
className="size-2 shrink-0 rounded-full bg-line"
/>
) : (
<MoodDot className="mr-0" level={clampLevel(avg)} />
)}
<span className="min-w-0 flex-1 truncate">{name}</span>
{avg === null ? (
<span className="text-muted-foreground text-xs">
sin datos
</span>
) : (
<>
<span className="text-muted-foreground text-xs">
{MOOD_NAMES[clampLevel(avg) - 1]}
</span>
<span className="w-8 text-right font-medium tabular-nums">
{avg.toLocaleString("es-AR", {
maximumFractionDigits: 1,
minimumFractionDigits: 1,
})}
</span>
<Trend delta={delta} />
</>
)}
</span>
</a>
</li>
))}
</ul>
<p className="m-0 text-ink-3 text-xs">
Muy bajo <1,8 · Bajo <2,6 · Neutral <3,4 · Bien <4,2 · Muy
bien
</p>
</RailCard>
);
}Próximos eventos
Hasta tres eventos del calendario con su fecha. Los de día completo se formatean en UTC.
Próximos eventos
Ver calendario →- 9octJornada deportivavie
- 14octCierre de mes de facturaciónmié
- 16octCierre de trimestrevie
Código
events.tsx
import { RailCard } from "@moodinary/ui/components/rail-card";
import { EventRow } from "@moodinary/ui/components/rail-row";
/* Igual que UpcomingEvents en apps/pulses/.../inicio-rail.tsx: hasta 3 eventos, título simple. */
const events = [
{ date: "2026-10-09", title: "Jornada deportiva" },
{ date: "2026-10-14", title: "Cierre de mes de facturación" },
{ date: "2026-10-16", title: "Cierre de trimestre" },
];
export default function RailEvents() {
return (
<RailCard
action={<a href="#calendario">Ver calendario →</a>}
className="w-[340px] max-w-full"
title="Próximos eventos"
>
<ul className="m-0 grid list-none gap-2.5 p-0 text-sm">
{events.map(({ date, title }) => (
<EventRow
date={new Date(date)}
key={title}
timeZone="UTC"
title={title}
/>
))}
</ul>
</RailCard>
);
}Props
RailCard
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| title | ReactNode | - | Título obligatorio, renderizado como h2. |
| action | ReactNode | - | Enlace o acción del encabezado. |
| children | ReactNode | - | Lista o contenido compacto. |
| className | string | - | Clases de la tarjeta; el ancho lo decide la composición. |
EventRow
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| date | Date | - | Fecha del evento. |
| title | ReactNode | - | Nombre del evento. |
| timeZone | string | - | Zona para formatear fecha y día. UTC es útil para fechas de día completo. |
| className | string | - | Clases de la fila li. |
Cuándo sí y cuándo no
Hacé
- Usá listas cortas que se puedan leer de un vistazo.
- Enlazá la vista completa con un nombre concreto.
- Mantené el rail secundario respecto del contenido principal.
Evitá
- No metas tablas completas en la columna lateral.
- No uses el color sin una palabra o un valor.
- No dejes una salida Ver todo que no lleve a ningún lado.
Accesibilidad
- EventRow renderiza li: envolvelo en ul u ol.
- El enlace del encabezado entra por Tab; las filas de lectura no deben robar foco.
- Nombrá las barras de ánimo con su valor o mantenelas decorativas si ese valor ya está escrito.