feat: interfaz clara unificada en Centro de Mando
Shell con nav agrupada y activa, PageHeader común, tipografía y tokens. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
95
src/components/ui.tsx
Normal file
95
src/components/ui.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
|
||||
/** Título de página unificado para todo el Centro de Mando. */
|
||||
export function PageHeader({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
actions,
|
||||
}: {
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
actions?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="mb-6 md:mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div className="min-w-0 space-y-1.5">
|
||||
{eyebrow ? (
|
||||
<p className="text-[10px] font-semibold uppercase tracking-[0.16em] text-brand-400/90">
|
||||
{eyebrow}
|
||||
</p>
|
||||
) : null}
|
||||
<h1 className="text-2xl md:text-3xl font-semibold tracking-tight text-white text-balance">
|
||||
{title}
|
||||
</h1>
|
||||
{description ? (
|
||||
<p className="text-sm text-gray-400 max-w-2xl leading-relaxed">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{actions ? (
|
||||
<div className="flex flex-wrap items-center gap-2 shrink-0">{actions}</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Superficie de contenido estándar (sustituye cards sueltas inconsistentes). */
|
||||
export function Surface({
|
||||
children,
|
||||
className = '',
|
||||
padded = true,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
padded?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`rounded-2xl border border-white/[0.06] bg-card/80 ${
|
||||
padded ? 'p-4 md:p-5' : ''
|
||||
} ${className}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SectionTitle({
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="mb-3">
|
||||
<h2 className="text-base font-semibold text-white">{title}</h2>
|
||||
{description ? (
|
||||
<p className="text-xs text-gray-500 mt-0.5">{description}</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Estado vacío / aviso de contexto reutilizable. */
|
||||
export function EmptyState({
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-dashed border-white/[0.1] bg-white/[0.02] px-6 py-10 text-center">
|
||||
<p className="text-sm font-medium text-gray-200">{title}</p>
|
||||
{description ? (
|
||||
<p className="text-xs text-gray-500 mt-1.5 max-w-md mx-auto leading-relaxed">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user