design: sistema visual de marca (Midnight/Orange, Canela, shell oscuro)
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
/** Título de página unificado para todo el Centro de Mando. */
|
||||
/**
|
||||
* Primitivas del Centro de Mando.
|
||||
* Reglas: un acento (naranja), etiquetas en frase normal, radio 12px,
|
||||
* jerarquía por tono de fondo y no por sombras.
|
||||
*/
|
||||
|
||||
/** Título de página unificado. El eyebrow se dibuja con la regla naranja del brandbook. */
|
||||
export function PageHeader({
|
||||
eyebrow,
|
||||
title,
|
||||
@@ -13,24 +19,25 @@ export function PageHeader({
|
||||
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">
|
||||
<div className="mb-7 md:mb-9 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
{eyebrow ? (
|
||||
<p className="text-[10px] font-semibold uppercase tracking-[0.16em] text-brand-600">
|
||||
<p className="mb-2 flex items-center gap-2 text-[13px] font-medium text-ink-muted">
|
||||
<span aria-hidden className="h-px w-5 bg-brand-500" />
|
||||
{eyebrow}
|
||||
</p>
|
||||
) : null}
|
||||
<h1 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 text-balance">
|
||||
<h1 className="display text-[2rem] leading-[1.05] md:text-[2.5rem] text-ink text-balance">
|
||||
{title}
|
||||
</h1>
|
||||
{description ? (
|
||||
<p className="text-sm text-gray-600 max-w-2xl leading-relaxed">
|
||||
<p className="mt-2.5 max-w-[62ch] text-[15px] leading-relaxed text-ink-muted">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{actions ? (
|
||||
<div className="flex flex-wrap items-center gap-2 shrink-0">{actions}</div>
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
@@ -48,8 +55,8 @@ export function Surface({
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`rounded-2xl border border-gray-200 bg-white shadow-sm ${
|
||||
padded ? 'p-4 md:p-5' : ''
|
||||
className={`rounded-xl border border-border bg-card ${
|
||||
padded ? 'p-5 md:p-6' : ''
|
||||
} ${className}`}
|
||||
>
|
||||
{children}
|
||||
@@ -65,15 +72,16 @@ export function SectionTitle({
|
||||
description?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="mb-3">
|
||||
<h2 className="text-base font-semibold text-gray-900">{title}</h2>
|
||||
<div className="mb-4">
|
||||
<h2 className="text-[17px] font-medium leading-tight text-ink">{title}</h2>
|
||||
{description ? (
|
||||
<p className="text-xs text-gray-500 mt-0.5">{description}</p>
|
||||
<p className="mt-1 text-[13px] text-ink-muted">{description}</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Estado vacío: dice qué pasa y qué hacer, sin decorar. */
|
||||
export function EmptyState({
|
||||
title,
|
||||
description,
|
||||
@@ -82,10 +90,10 @@ export function EmptyState({
|
||||
description?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-dashed border-gray-300 bg-gray-50 px-6 py-10 text-center">
|
||||
<p className="text-sm font-medium text-gray-800">{title}</p>
|
||||
<div className="rounded-xl border border-dashed border-mist-300 bg-mist-50 px-6 py-12 text-center">
|
||||
<p className="text-[15px] font-medium text-ink">{title}</p>
|
||||
{description ? (
|
||||
<p className="text-xs text-gray-600 mt-1.5 max-w-md mx-auto leading-relaxed">
|
||||
<p className="mx-auto mt-1.5 max-w-md text-[13px] leading-relaxed text-ink-muted">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -93,7 +101,7 @@ export function EmptyState({
|
||||
);
|
||||
}
|
||||
|
||||
/** Selector de periodo / pestañas — contraste alto sobre fondo blanco. */
|
||||
/** Selector de periodo / pestañas. Activo en tinta sobre blanco; el naranja se reserva para acciones. */
|
||||
export function SegmentedControl<T extends string>({
|
||||
value,
|
||||
options,
|
||||
@@ -105,18 +113,19 @@ export function SegmentedControl<T extends string>({
|
||||
onChange: (id: T) => void;
|
||||
size?: 'sm' | 'md';
|
||||
}) {
|
||||
const pad = size === 'sm' ? 'px-2.5 py-1 text-xs' : 'px-3 py-1.5 text-sm';
|
||||
const pad = size === 'sm' ? 'px-2.5 py-1 text-[13px]' : 'px-3 py-1.5 text-sm';
|
||||
return (
|
||||
<div className="flex gap-1 rounded-lg border border-gray-200 p-0.5 bg-gray-100">
|
||||
<div className="inline-flex gap-0.5 rounded-md border border-border bg-mist-100 p-0.5">
|
||||
{options.map((o) => (
|
||||
<button
|
||||
key={o.id}
|
||||
type="button"
|
||||
onClick={() => onChange(o.id)}
|
||||
className={`${pad} rounded-md font-semibold transition-colors ${
|
||||
aria-pressed={value === o.id}
|
||||
className={`${pad} rounded-sm font-medium transition-colors ${
|
||||
value === o.id
|
||||
? 'bg-brand-500 text-white shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900 hover:bg-white'
|
||||
? 'bg-ink text-white'
|
||||
: 'text-ink-muted hover:bg-white hover:text-ink'
|
||||
}`}
|
||||
>
|
||||
{o.label}
|
||||
|
||||
Reference in New Issue
Block a user