Cayaqui.MPS.Components 0.34.0

dotnet add package Cayaqui.MPS.Components --version 0.34.0
                    
NuGet\Install-Package Cayaqui.MPS.Components -Version 0.34.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Cayaqui.MPS.Components" Version="0.34.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Cayaqui.MPS.Components" Version="0.34.0" />
                    
Directory.Packages.props
<PackageReference Include="Cayaqui.MPS.Components" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Cayaqui.MPS.Components --version 0.34.0
                    
#r "nuget: Cayaqui.MPS.Components, 0.34.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Cayaqui.MPS.Components@0.34.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Cayaqui.MPS.Components&version=0.34.0
                    
Install as a Cake Addin
#tool nuget:?package=Cayaqui.MPS.Components&version=0.34.0
                    
Install as a Cake Tool

Cayaqui.MPS.Components

Design system + componentes EVM/EPC reutilizables para .NET 10 Blazor WebApp (Interactive Server) construidos sobre Syncfusion.Blazor 33.x.

v0.34.0 — Skeleton composites para arquetipos de loading-state

Cinco composites nuevos sobre MpsSkeleton cubren los arquetipos comunes de loading-state — eligen el correcto y reducís layout shift sin reinventar el primitivo:

<MpsSkeletonStack />                          @* N líneas (forms, sidebars, párrafos) *@
<MpsSkeletonCard Height="280px" />            @* header + rect (chart placeholder) *@
<MpsSkeletonRow />                            @* avatar + título + subtítulo *@
<MpsSkeletonGrid />                           @* 1×4 KPI strip por default *@
<MpsSkeletonTable Rows="15" />                @* toolbar + 15 filas *@

Composición típica de dashboard:

@if (_loading)
{
    <MpsSkeletonGrid Rows="1" Columns="4" />              @* KPI strip *@
    <MpsSkeletonCard Height="280px" />                    @* chart *@
    <MpsSkeletonTable Rows="6" ShowToolbar="false" />     @* tabla resumen *@
}

Todos consumen internamente <MpsSkeleton/> (heredan shimmer + prefers-reduced-motion). +37 tests bUnit (798 total). Sin breaking changes — son aditivos sobre la v0.33.0. Ver scripts/migrate-to-components-0.34.0.md para recetas por arquetipo.


v0.33.0 — MpsSkeleton runtime fix

Bug crítico runtime (presente desde la primera versión): MpsSkeleton.razor rendereaba <div class="mps-skeleton ..."> pero la regla CSS empacada era solo .skeleton. El componente nunca aplicó shimmer ni estilos en consumidores. En 0.33.0 la clase y el markup quedan alineados (.mps-skeleton), @keyframes shimmer se namespacea como mps-skeleton-shimmer, y se agregan defaults dimensionales por shape:

<MpsSkeleton />                @* line · 100% × 0.875rem *@
<MpsSkeleton Shape="rect" />   @* rect · 100% × 6rem *@
<MpsSkeleton Shape="circle" /> @* circle · 2.5rem × 2.5rem *@

<MpsSkeleton /> sin parámetros ahora es visible. Cualquier llamada que ya pasaba Width/Height mantiene el override. Soporte prefers-reduced-motion: reduce (WCAG 2.3.3). +7 tests bUnit (761 total).

Acción opcional para consumers: si tu app aplicó el hotfix consumer-side (regla .mps-skeleton en wwwroot/app.css), podés removerlo — el paquete ya la incluye. Ver scripts/migrate-to-components-0.33.0.md. Sin breaking changes.


v0.32.0 — MpsPageHeader browser tab + Double/TripleProgress 90% opacidad

Fix bug: la versión anterior tenía <PageTitle>@Title - MOVES</PageTitle> hardcodeado en MpsPageHeader — toda app no-MOVES recibía "MOVES" en sus pestañas. Ahora el sufijo es configurable vía ThemeOptions.AppName:

// Program.cs
builder.Services.AddMpsComponents(o => o.AppName = "MOVES");

MpsPageHeader emite automáticamente <PageTitle>{Title} · {AppName}</PageTitle>. Overrides:

  • SetBrowserTab="false" → no emite (tu page maneja su propio <PageTitle>).
  • BrowserTabTitle="Corto" → título distinto al del header visual.
  • BrowserTabSuffix="Demo" → sufijo por instancia (override de AppName).
  • BrowserTabSuffix="" → fuerza sin sufijo.

DoubleProgress / TripleProgress: barras a 90% opacidad (antes 50%) para mejor legibilidad EVM.


v0.31.0 — CashflowChart leyenda en CutOffDate

La leyenda del chart ahora se renderiza como HTML propio debajo del gráfico, con dos grupos:

  • Mes de cutoff · valor del mes de control para Plan / Real / Forecast (columnas). Forecast muestra el total restante.
  • Acumulado al cutoff · cumulativos al cierre de la fecha de control. Forecast acum. = EAC (Real cum + Forecast restante).

Si CutOffDate es null, la leyenda muestra los totales del rango. Cada entrada usa MoneyDisplay (formato consistente con el resto del design system) y un swatch que replica el estilo visual del chart (columnas 65% opacidad, líneas sólidas, Forecast acum. dashed verde).

Sin cambios de API. Helper estático CashflowChart.ComputeLegendValues(raw, cutOff) para tests. Migración: si testeás contra .cf-summary (header anterior), migrá a .cf-legend.


v0.30.0 — CashflowChart con acumulados

CashflowChart ahora muestra columnas mensuales + líneas acumuladas en el mismo gráfico (eje Y secundario):

  • Columnas (eje primario, monto mensual): Plan en todos los meses, Real ≤ CutOffDate, Forecast > CutOffDate (filtrado automático).
  • Líneas acumuladas (eje secundario): Plan acum., Real acum., Forecast acum. — esta última arranca en el Real acum. del cutoff para continuidad visual con la línea Real.
<CashflowChart Points="@cashflow"
               Currency="USD"
               CutOffDate="@DateTime.Today"
               Title="Cashflow mensual · Talara U3" />

ShowCumulative="false" para volver al aspecto 0.29.x (sólo columnas). El parámetro ControlDate queda como alias retro-compat de CutOffDate. Sin breaking de API; +10 tests; demo en /catalogo/evm#CashflowChart.


v0.29.0 — Tipografía dual MPS (Inter + Roboto Condensed)

Convención:

  • Inter → cuerpo, labels, títulos, botones — "el resto del contenido".
  • Roboto Condensed → charts, tablas, valores numéricos, KPIs, paginadores, códigos tabulares.
  • JetBrains Mono (reservado para --font-mono / .kbd) → atajos de teclado, snippets de código.

Tokens nuevos:

:root {
  --font-sans:    'Inter', system-ui, -apple-system, sans-serif;
  --font-numeric: 'Roboto Condensed', 'Inter', system-ui, sans-serif;
  --font-table:   var(--font-numeric);
  --font-chart:   var(--font-numeric);
  --font-mono:    'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}

Recomendación de loading (LCP óptimo). mps-tokens.css ya trae un @import con las fuentes/pesos, pero @import bloquea el render. Para tu host page preferí <link> en App.razor:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500;700&family=Inter:wght@300;400;500;600;700&display=swap">

Aplicado automáticamente a:

  • Charts: SfChart, AccumulationChart, StockChart, RangeNavigator, Sparkline, Gauges, Gantt, PivotView (vía selector text SVG).
  • Grids: SfGrid, SfTreeGrid, SfGantt + tablas custom (.mps-table, .mps-po-table, .mps-deliv-table, .mps-risk-table, .mps-svt-table).
  • Componentes con valores numéricos: MoneyDisplay, MpsCounter, MpsRangeSlider, CurrencyInput, KpiCard, CpiIndicator, EarnedScheduleIndicator, VarianceIndicator, ResourceHistogram, StockpileLevel, R9cTable, PurchaseOrderRegister, AttachmentList, DonutChart, MpsGauge, MpsBulletGauge.

Override por consumer (mantener apariencia 0.28.x con JetBrains Mono en valores numéricos):

:root { --font-numeric: var(--font-mono); }

Sin breaking changes.


v0.28.0 — Theme SSR (elimina FOUC)

Si tu app usa un DefaultAccent distinto a Emerald, el cold load mostraba un flash visible de Emerald → tu accent (~300ms). Causa: la paleta vivía como CSS estático Emerald + JS interop que la reescribía post-paint. Fix: nuevos selectors :root[data-mps-accent="..."] + helper MpsThemeAttributes para SSR.

Patrón canónico (sin más workarounds inline):

@* App.razor *@
@inject IOptions<ThemeOptions> ThemeOpts
<!DOCTYPE html>
<html lang="es" @attributes="MpsThemeAttributes.For(ThemeOpts.Value)">
    <head>
        ...
    </head>
</html>
// Program.cs
builder.Services.AddMpsComponents(opts => opts.DefaultAccent = ThemeAccent.Cobalt);

Resultado: el HTML SSR-rendered ya tiene <html data-mps-accent="cobalt"> antes del primer paint → cero flicker. El ThemeService.SetAsync runtime sigue funcionando para overrides per-user (CSS specificity garantiza que el setProperty inline gane sobre el attribute selector).

5 accents soportados con paleta completa (50→800 stops): Cobalt, Indigo, Emerald (default), Amber, Slate. 3 densities (Compact, Comfort, Spacious) y 2 directions (A warm/sobria, B cool/expresiva) también soportan SSR via data-mps-density y data-mps-direction.

Sin breaking changes — los consumers que no adoptan el patrón siguen funcionando con Emerald como default.


v0.27.0 — Tier 1 native migration (sin breaking changes)

4 componentes core que internamente wrappeaban Syncfusion ahora son HTML nativo:

  • MpsButton<button> con CSS existente (mps-btn primary/secondary/tertiary/link/destructive).
  • MpsIconButton<button class="mps-btn icon-only">.
  • MpsRadio<input type="radio"> con role="radiogroup" y keyboard handling del browser.
  • MpsTabs<div role="tablist"> con <button role="tab"> y keyboard nav WAI-ARIA (← → / ↑ ↓ / Home / End).

API pública conservada — drop-in upgrade. Las clases CSS internas cambiaron: Ghost ahora emite class="mps-btn tertiary" y Danger emite destructive. Si tu CSS custom tenía selectores .mps-btn.ghost/.danger, hay que adaptarlos.

Por qué la migración: independencia de runtime de Syncfusion para los componentes más usados, control completo del CSS, menor superficie de licencia para estos cuatro. Tier 2 (MpsDialog<dialog>, MpsDrawer, MpsToastHost) en una release futura.


v0.26.0 — UX hardening (5 fixes/features desde feedback)

🐛 Bug fix — MpsRangeSlider tooltip: Syncfusion 33.x no interpola C# format strings en SliderTooltip.Format — el tooltip mostraba "USD {600000:N0} - USD {30000000:N0}" literal. Fix: usar OnTooltipChange callback que construye el texto en C#.

💰 Smart compact money — MpsMoneyScope + nueva regla MPS:

@* Antes: cada MoneyDisplay decidía su escala individualmente *@
<MoneyDisplay Value="450_000m" Compact="true" />     @* "USD 450K" *@
<MoneyDisplay Value="3_500_000m" Compact="true" />   @* "USD 3.5M" — distinto criterio *@

@* v0.26.0: scope unifica escala basado en max value *@
<MpsMoneyScope Values="@(new [] { 450_000m, 3_500_000m })">
    <MoneyDisplay Value="450_000m"  Compact="true" />     @* "MUSD 0,5" *@
    <MoneyDisplay Value="3_500_000m" Compact="true" />    @* "MUSD 3,5" *@
</MpsMoneyScope>

Regla MPS nueva: kUSD con 0 decimales (kUSD 450); MUSD con 1 decimal (MUSD 4.500,1 en es-CL). Una página entera usa la escala del monto mayor — coherencia visual.

Breaking en MoneyDisplay compact: el output cambió de "USD 10.5M" (suffix) a "MUSD 10,5" (prefix). Añadidos params Scale: MoneyScale?, Culture: CultureInfo?, y CompactDecimals ahora es int? con default por-escala (override solo si querés saltarte la regla).

🌐 MpsCounter cultura: ahora hereda CultureInfo.CurrentCulture (era InvariantCulture). Con DefaultThreadCurrentCulture = es-CL en MPS.Web, los números se formatean automáticamente con punto thousands y coma decimal.

💬 MpsButton + MpsIconButton — Tooltip param:

<MpsButton Tooltip="Guardar cambios pendientes">Guardar</MpsButton>
<MpsIconButton Icon="e-icons e-edit" Tooltip="Editar fila" />

Renderiza title (browser tooltip nativo) + aria-label (a11y) si AriaLabel no se especificó explícitamente.

🎨 MpsCard — header bg + line toggle:

@* Header con tint del Variant + sin línea inferior *@
<MpsCard Title="Resumen"
         Variant="MpsCardVariant.Success"
         HeaderTinted="true"
         HeaderBorder="false">
    ...
</MpsCard>

HeaderTinted (default false) aplica --mps-card-tint al fondo del header. HeaderBorder (default true) controla la línea inferior. Combinados generan un header con look cohesionado al body.


v0.25.0 — MpsEnumSelectRequired + Metadata 0.4.0

MpsEnumSelect<TEnum> (introducido en v0.23) requiere Value: TEnum? (nullable struct) para soportar el placeholder "Seleccionar...". Esto NO compila con forms que tienen propiedades non-nullable:

@* NO COMPILA si _form.Status es 'Status' (non-nullable) *@
<MpsEnumSelect TEnum="Status" @bind-Value="_form.Status" />

MpsEnumSelectRequired<TEnum> es una sister component aditiva con Value: TEnum para el caso non-nullable:

@* OK con _form.Status: Status non-nullable *@
<MpsEnumSelectRequired TEnum="Status" @bind-Value="_form.Status" />

Cuándo usar cuál:

Escenario Componente
Form con [Required] + non-nullable enum prop <MpsEnumSelectRequired>
Filtro que permite "todas las opciones" (sin selección) <MpsEnumSelect> (nullable)

Bump transitivo: Cayaqui.MPS.Metadata 0.4.0+ — los enums con [Display(Name)] ahora se renderizan con la etiqueta localizada en MpsAutoGrid, MpsAutoBadge, MpsAutoForm y los renderers de Cayaqui.MPS.Reports.

v0.24.0 — AutoGrid Rich Renderers

MpsAutoGrid ahora rinde automáticamente celdas con componentes ricos del design system según attributes/types del DTO:

  • <StatusChip> automático cuando una propiedad del DTO es de tipo StatusChip.WorkflowStatus (auto-detect, sin atributo).
  • <UserChip> automático cuando se decora con [UserColumn(AvatarSource, RoleSource)] (atributo nuevo en Cayaqui.MPS.Metadata 0.3.0). Apunta a propiedades hermanas del DTO.
  • Refactor de Status/AutoBadge cells: ahora rinden con <MpsBadge> componente real (con MpsBadgeVariant enum tipado v0.20+) en lugar de <span class="mps-badge"> inline.
public class ChangeOrderDto
{
    public string Code { get; set; } = "";
    public StatusChip.WorkflowStatus Status { get; set; }   // → automático <StatusChip>

    [UserColumn(AvatarSource = nameof(OwnerAvatar), RoleSource = nameof(OwnerRole))]
    public string OwnerName { get; set; } = "";   // → automático <UserChip>
    public string? OwnerAvatar { get; set; }
    public string? OwnerRole { get; set; }
}

Sin breaking changes. Bumpea dependencia de Cayaqui.MPS.Metadata a 0.3.0. Migración en scripts/migrate-to-components-0.24.0.md.

v0.23.1 — Fix MpsNumeric culture (es-CL)

MpsNumeric ahora formatea + parsea con culture es-CL (. thousands, , decimal) en lugar de Invariant. Default Format=N2 con 1234.5m rinde "1.234,50". Sin breaking change.

v0.23.0 — Forms HTML-native + MpsEnumSelect

Los 6 componentes de formulario simples (MpsTextBox, MpsNumeric, MpsSelect, MpsInputMask, MpsCheckBox, MpsToggle) migran de wrappers Syncfusion a HTML-native:

  • Cero JS interop → SSR más confiable y primer paint más rápido
  • Tests sin [Collection("Syncfusion")] — no necesitan serialización
  • Bundle reducido: drop de Syncfusion.Blazor.DropDowns

Nuevo: MpsEnumSelect<TEnum> con auto-projection desde Enum.GetValues y labels desde [Display(Name="…")]:

public enum Status { [Display(Name="Aprobado")] Approved, ... }

<MpsFormField Label="Estado">
    <MpsEnumSelect TEnum="Status" @bind-Value="_status" />
</MpsFormField>

Breaking changes (2, mecánicos): MpsTextBox.InputType enum cambia (era SF, ahora propio); MpsSelect TextField/ValueFieldTextSelector/ValueSelector. Ver scripts/migrate-to-components-0.23.0.md.


Suite EPC completo + design system con 70 componentes activos del roadmap publicado (v0.20.0 — 5 nuevos: Accordion, Timeline, FileUploader, InputMask, AvatarGroup + improvements Button.Loading/Badge enum/Alert enum/Avatar.Status; v0.19.0 — Cards suite: variants/accents/behaviors + 4 nuevos cards especializados + token --color-info-*; v0.18.4 — fix MpsSelect popup + docs sidebar accent override; v0.18.3 — MpsPageHeader breadcrumbs auto-generados + acento izquierdo del tema en el card + tipografía refinada; v0.18.2 — fix 19 constantes MpsIcons con nombres CSS inválidos en Syncfusion; v0.18.1 — fix MpsDialog compatible con Syncfusion 33.2.3; v0.18.0 — MpsIcons ~150 constantes, IRouteLabelsProvider para breadcrumbs automáticos con label+icono, MpsPageHeader.Card mode; v0.17.0 — RiskHeatmap v2 chips/tooltip/filtro; v0.16.0 — UserProfileCard + UserChip.PopoverContent). Agrupados por sección:

  • Design system (Buttons · Forms · Data Display · Navigation · Overlays · Feedback · StatusChip)
  • Auto-metadata (v0.2.0) — MpsAutoGrid<T>, MpsAutoForm<TModel>, MpsAutoFormField<T>, MpsAutoBadge que leen attrs del DTO desde Cayaqui.MPS.Metadata
  • EVM / EPC (KPI strip · gauges CPI/SPI/PF · curvas S · Gantt · WBS tree grid · R9C · Risk heatmap · Change orders · Purchase orders · Engineering deliverables · LookaheadGrid Last Planner)
  • Project controls completos — P3 Schedule (MilestoneStrip, ResourceHistogram, CriticalPathSummary, ScheduleVarianceTable, BurndownChart) · P4 Risk (RiskRegisterTable, TornadoChart, MonteCarloHistogram, RiskBoard, RiskTrendLine) · P5 Scope/Change Control (ApprovalWorkflow, MpsStepper, ConfirmDialog, AttachmentList, RaciMatrix) · P6 Forms de dominio (WbsPicker, PeriodNavigator, DateRangePicker, CurrencyInput, CodedTextbox, SearchCombo) · P7 Data display genérico (BarChart, DonutChart, StackedBarChart, UserChip, UserProfileCard, Banner, EmptyStateIllustrated) · P8 Mining-specific (CommodityPriceChart, StockpileLevel, ProductionCurve, ShiftSchedule)

Distribución propietaria — requiere contrato comercial con Cayaqui. Ver LICENSE.txt.

Instalación

dotnet add package Cayaqui.MPS.Components

Registro de servicios

// Program.cs
using MPS.Components;

builder.Services.AddMpsComponents();          // ThemeService + ToastService (scoped)
// Override de defaults:
builder.Services.AddMpsComponents(opts =>
{
    opts.DefaultAccent       = ThemeAccent.Emerald;
    opts.DefaultDensity      = ThemeDensity.Comfort;
    opts.DefaultRadiusScale  = 10m / 6m; // "10 · Redondeado"
});

La persistencia por usuario del tema (guardar accent/density/radius en BD) no forma parte de este paquete — se implementa con IThemePersistence (interfaz expuesta aquí) y un módulo de administración propio del consumidor.

Imports globales

En _Imports.razor:

@using MPS.Components.Design          ← MpsIcons y otros tipos raíz
@using MPS.Components.Design.Buttons
@using MPS.Components.Design.Forms
@using MPS.Components.Design.DataDisplay
@using MPS.Components.Design.Navigation
@using MPS.Components.Design.Overlays
@using MPS.Components.Design.Feedback
@using MPS.Components.Evm
@using MPS.Components.Theming

Iconos — MpsIcons

MpsIcons es una clase estática de constantes (MPS.Components.Design) que centraliza las clases CSS Syncfusion (e-icons e-*) usadas en toda la suite. Evita strings hardcodeados dispersos y hace los usos refactorizables.

// Namespace — ya incluido si seguís los imports recomendados:
@using MPS.Components.Design

Uso básico

<MpsButton IconLeft="@MpsIcons.Add">Nuevo CA</MpsButton>
<MpsButton IconLeft="@MpsIcons.ExportExcel" Variant="MpsButton.ButtonVariant.Ghost">Exportar</MpsButton>
<MpsIconButton Icon="@MpsIcons.Edit"   AriaLabel="Editar"   Size="MpsButton.ButtonSize.Sm" />
<MpsIconButton Icon="@MpsIcons.Delete" AriaLabel="Eliminar" Size="MpsButton.ButtonSize.Sm" />

En items de navegación

new NavItem("Dashboard",        "/",         MpsIcons.Home,        IsActive: true)
new NavItem("Control Accounts", "/ca",       MpsIcons.GridView)
new NavItem("WBS",              "/wbs",      MpsIcons.Folder,      Badge: "16")
new NavItem("Cronograma",       "/schedule", MpsIcons.Schedule)
new NavItem("Riesgos",          "/risk",     MpsIcons.Warning)

Grupos disponibles (~150 constantes)

Grupo Ejemplos
Actions Add, Edit, Delete, Save, Download, Upload, Filter, Search, Undo, Redo, …
Export ExportExcel, ExportCsv, ExportPdf, ExportWord, ExportPng, …
Navigation Home, Folder, FolderOpen, Menu, Expand, Collapse, Pin, OpenLink, …
Arrows & Chevrons ArrowDown/Left/Up/Right, ChevronDown/Up/Left/Right + fills
Files & Attachments File, FilePdf, FileDocument, Attachment, Image, Link, …
People & Identity User, People, Password, Location
Status / Feedback Check, CircleCheck, CircleInfo, Warning, Clock, History, Star, …
Data & Tables Table, GrandTotal, SubTotal, Calculation, Level1Level5, …
Charts Chart, ChartLine, ChartDonut, ChartScatter, ChartColumn, …
EVM / Project Controls Kpi, CriticalPath, GanttGripper, TimelineDay/Week/Month, …

La galería completa con búsqueda y preview está en /catalogo/icons del proyecto MPS.Web de referencia.

Estáticos

Los CSS/JS se sirven automáticamente bajo _content/MPS.Components/. Snippet recomendado en App.razor — un único bundle más el theme de Syncfusion (que viaja como dependencia transitiva):


<link rel="stylesheet" href="_content/Syncfusion.Blazor.Themes/tailwind3.css" />


<link rel="stylesheet" href="_content/MPS.Components/css/mps-bundle.css" />

<script src="_content/MPS.Components/js/mps-theme.js"></script>

El theme Syncfusion debe ir antes que mps-bundle.css para que mps-syncfusion-overrides.css (incluido en el bundle) tenga la última palabra.

Cargar CSS individuales (opcional)

Si prefieres tree-shake manual o cargar sólo un subset, sustituye mps-bundle.css por los archivos puntuales en este orden:

<link rel="stylesheet" href="_content/MPS.Components/css/mps-tokens.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-syncfusion-overrides.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-components.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-indicators.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-evm-extras.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-wbs-treegrid.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-gantt.css" />

Requisitos

  • .NET 10.0 o superior
  • Syncfusion.Blazor 33.2.3+ — el consumidor debe registrar su propia licencia Syncfusion:
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("YOUR_LICENSE_KEY");

Defaults del tema

Propiedad Default
Accent Emerald
Density Comfort
RadiusScale 10 / 6 (opción "10 · Redondeado")

Configurable vía ThemeOptions al registrar AddMpsComponents(opts => { ... }).

Auto-metadata (v0.2.0)

Con un DTO decorado usando atributos de Cayaqui.MPS.Metadata, los componentes MpsAutoGrid<T> y MpsAutoForm<TModel> generan columnas y campos automáticamente sin ceremonia manual.

public sealed class ControlAccountRow
{
    [Label("Código"), Wbs]                           public string  Code { get; set; } = "";
    [Label("Nombre"), Display(Order = 1)]            public string  Name { get; set; } = "";
    [Currency("USD")]                                public decimal Bac { get; set; }
    [Percentage(0)]                                  public decimal PctComplete { get; set; }
    [EvmIndicator(EvmKind.Cpi), EvmBand]             public decimal Cpi { get; set; }
    [Badge]                                          public CaPhase Phase { get; set; }
    [Hidden]                                         public Guid    Id { get; set; }
}
public enum CaPhase
{
    [BadgeColor(BadgeKind.Brand)]   Engineering,
    [BadgeColor(BadgeKind.Purple)]  Procurement,
    [BadgeColor(BadgeKind.Warning)] Construction
}
<MpsAutoGrid TItem="ControlAccountRow" DataSource="@rows" Title="Control Accounts" />
<MpsAutoForm TModel="ControlAccountRow" Model="@row" OnValidSubmit="SaveAsync" />

La dependencia Cayaqui.MPS.Metadata se auto-instala como transitive y AddMpsComponents() también llama internamente a AddMpsMetadata().

Layout shell (v0.4.0) — MpsSidebar + MpsAppHeader

Ambos componentes siguen Untitled UI Pro v8. Patrón canónico para una app:

@* MainLayout.razor *@
@inherits LayoutComponentBase
@inject ThemeService Theme

<div class="app-shell">
    <MpsSidebar @ref="_sidebar"
                Tone="MpsSidebar.SidebarTone.Branded"
                BrandLogo="@(new MpsSidebar.SidebarLogo(
                    Src:     \"/img/brand-app.svg\",       @* 200×40 lockup horizontal *@
                    SlimSrc: \"/img/brand-app-slim.svg\",  @* 36×36 icon/iso          *@
                    Alt:     \"Mi App\"))"
                CompanyLogo="@(new MpsSidebar.SidebarLogo(
                    Src:     \"/img/owner-logo.svg\",       @* 160×28 lockup horizontal *@
                    SlimSrc: \"/img/owner-logo-slim.svg\",  @* 28×28 icon/iso          *@
                    Alt:     \"Owner Inc\"))"
                Collapsible="true"
                CollapseStorageKey="myapp.sidebar.collapsed">
        <ChildContent>
            <MpsSidebarSection>
                <MpsSidebarItem Text="Inicio" Href="/" Match="MpsSidebar.ItemMatch.Exact">
                    <Icon><svg width="18" height="18" ...></svg></Icon>
                </MpsSidebarItem>
            </MpsSidebarSection>

            <MpsSidebarSection Label="Control">
                <MpsSidebarItem Text="Proyectos" Href="/projects" Badge="12">
                    <Icon><svg ...></svg></Icon>
                </MpsSidebarItem>
            </MpsSidebarSection>
        </ChildContent>

        <Footer>
            <MpsSidebarUser Name="Ana Pérez" Subtitle="ana@cayaqui.com" />
        </Footer>
    </MpsSidebar>

    <div class="app-content">
        <MpsAppHeader Sidebar="_sidebar"
                      ShowHamburger="true"
                      ShowSearch="true"
                      SearchPlaceholder="Buscar…"
                      OnSearch="@HandleSearch"
                      UserName="Ana Pérez"
                      UserSubtitle="ana@cayaqui.com">
            <UserMenu>
                <a href="/perfil">Mi perfil</a>
                <a href="/config">Configuración</a>
                <button @onclick="SignOut">Cerrar sesión</button>
            </UserMenu>
        </MpsAppHeader>

        <main class="app-main">
            @Body
        </main>
    </div>
</div>

@code {
    private MpsSidebar? _sidebar;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender) await Theme.InitializeAsync();
    }

    private void HandleSearch(string q) { /* navegar / filtrar */ }
    private async Task SignOut() { /* ... */ }
}

CSS mínimo del shell (en app.css del consumer — el bundle no incluye estos selectores porque varían según el layout):

.app-shell    { display: flex; min-height: 100vh; background: var(--color-bg); }
.app-content  { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.app-main     { flex: 1; min-width: 0; padding: 32px; }

MpsSidebar — opciones

Parámetro Default Notas
Variant Application (280px) Slim (72px solo iconos)
Tone Neutral Tinted (lavado accent-50) o Branded (accent-700 con texto claro)
BrandLogo (v0.15.0) null SidebarLogo(Src, SlimSrc?, Alt?). Logo del producto en el header. App: 200×40 px. Slim: 36×36 px.
CompanyLogo (v0.15.0) null SidebarLogo(Src, SlimSrc?, Alt?). Logo del owner en franja inferior. App: 160×28 px. Slim: 28×28 px.
Sticky true Sticky al top del viewport (position: sticky; top: 0; height: 100vh). Setear false si la sidebar va embebida en un layout que no quiere ese comportamiento.
Collapsible false Toggle Application↔Slim en el header.
CollapseStorageKey null Si seteás un key, el estado collapsed persiste por usuario en localStorage.
AutoActive true Infiere item activo desde NavigationManager.Uri.
ChildContent null Modo declarativo (<MpsSidebarSection> + <MpsSidebarItem>).
Sections [] Modo data-driven legacy con NavSection/NavItem records.

Sticky requiere viewport scroll. Si tu shell tiene un ancestor con overflow: auto o overflow: hidden propio, position: sticky se ancla a ese ancestor — usualmente no es lo que querés. Mantené el shell sin overflow propio (el body scrollea) y el sticky funciona como espera el patrón Untitled UI v8.

MpsSidebar soporta de forma tipada dos logos parametrizables: del producto (header) y de la compañía/owner (franja inferior, opcional). Cada logo se declara con dos variantes — lockup horizontal para Application mode y icon/iso square para Slim/Collapsed.

<MpsSidebar Tone="MpsSidebar.SidebarTone.Branded"
            BrandLogo="@(new MpsSidebar.SidebarLogo(
                Src:     "/img/brand-myapp.svg",
                SlimSrc: "/img/brand-myapp-slim.svg",
                Alt:     "MyApp Suite"))"
            CompanyLogo="@(new MpsSidebar.SidebarLogo(
                Src:     "/img/owner-logo.svg",
                SlimSrc: "/img/owner-logo-slim.svg",
                Alt:     "Owner Inc"))"
            Collapsible="true"
            CollapseStorageKey="myapp.sidebar.collapsed">
    @* ChildContent / Footer slots ... *@
</MpsSidebar>
Tipo público
public sealed record SidebarLogo(string Src, string? SlimSrc = null, string? Alt = null);
  • Src — URL del lockup horizontal (Application mode).
  • SlimSrc — URL del icon/iso 1:1 (Slim mode o Collapsed=true). Opcional pero recomendado. Si es null, se reusa Src y puede verse apretado/deformado.
  • Alt — texto alt para accesibilidad.
Dimensiones recomendadas de los assets
Slot Application (sidebar 280px) Slim/Collapsed (sidebar 72px)
Brand (producto) 200×40 px 36×36 px
Company (owner) 160×28 px 28×28 px
  • SVG preferido (escalable, sin pérdida en retina). PNG aceptable a @2x (400×80, 72×72, 320×56, 56×56). Evitar JPG (sin alpha).
  • Las imágenes deben llegar trim (sin padding baked-in). El padding interno lo provee el slot CSS (.sb-header, .sb-company).
  • CompanyLogo se renderiza con opacity: 0.75 (Neutral/Tinted) ó 0.85 (Branded) — visualmente subordinado al brand del producto.
Tone="Branded" + logos

En modo Branded (sidebar pintado con accent-700), los logos a color sobre el fondo oscuro pueden no leer bien. Dos opciones:

  1. Recomendado: proveer un Src apuntando a la versión blanca/monocroma del logo (ej. logo-white.svg).
  2. Si tu logo es monocromo con alpha (typical SVG con fill="#000" o sin fill explícito), agregar la utility class invert-on-branded al <img> del logo invierte automáticamente a blanco vía filter: brightness(0) invert(1). La utility está disponible globalmente — no requiere parámetro adicional.
Layout en runtime
Application (280px)             Slim (72px)
┌──────────────────────────┐    ┌──────┐
│ [logo MyApp lockup]   ⇄  │    │ [Mi] │  ← BrandLogo.SlimSrc
├──────────────────────────┤    ├──────┤
│ ▸ Dashboard              │    │  ▦   │
│ ▸ Proyectos          12  │    │  □   │
│ ▸ Riesgos                │    │  ⚠   │
├──────────────────────────┤    ├──────┤
│ [Footer slot — user]     │    │ [Av] │  ← Footer slot (opcional, MpsSidebarUser)
├──────────────────────────┤    ├──────┤
│ [logo Owner Inc]         │    │ [Ow] │  ← CompanyLogo.SlimSrc
└──────────────────────────┘    └──────┘

Coexistencia con Footer slot: si declarás Footer (típicamente MpsSidebarUser) y CompanyLogo, ambos se apilan (Footer arriba, CompanyLogo abajo). El divisor entre ambos se suprime automáticamente para evitar doble línea (regla CSS > .sb-footer + .sb-company).

Precedencia en el header: si pasás un Header RenderFragment custom, ignora BrandLogo y BrandMark. Si pasás BrandLogo, ignora BrandMark. La intención es que cada nivel de override sea opt-in claro.

MpsAppHeader — opciones

Parámetro Default Notas
Sticky true Sticky al top del area de contenido.
ShowHamburger false Muestra el botón menú a la izquierda. Si querés que aparezca solo en mobile, condicionalo en el consumer (no se fuerza por CSS).
Sidebar null Ref al MpsSidebar. Si está seteado, el hamburger toggle-a Collapsed automáticamente.
OnHamburgerClick Override manual; toma precedencia sobre Sidebar.
ShowSearch false Renderiza un <input type="search"> con icono. OnSearch se dispara al apretar Enter.
UserName / UserSubtitle / UserImage Si UserName no es null, renderiza el dropdown de usuario.
UserMenu Slot con los items del dropdown (links, botones). Click cierra el menú.
Left / Center / Right Slots libres. Center se oculta en <768px.

Recomendaciones de uso

  • Usá una sola instancia de MpsAppHeader y MpsSidebar por shell. Para shells anidados (admin / contractor portal) duplicá el shell completo.
  • Tono de la sidebar: Branded para shell global (la marca visible). Si tenés un sub-shell (ej. configuración), usar Tinted o Neutral para diferenciar jerarquía.
  • Sidebar collapsed default: si tu app tiene una densidad alta de items, considerá iniciar con Collapsed=true y dejar al usuario expandir.
  • Iconos: SVG inline 18×18 con stroke="currentColor" heredan el color del item según estado (hover/active/branded). Evitá íconos rasterizados.
  • Auth-aware: el componente no decide qué items mostrar; envolvé <MpsSidebarItem> con <AuthorizeView Roles="..."> cuando necesites filtrar por rol.

Accesibilidad de formularios (v0.4.2+)

MpsFormField ahora cascadea un MpsFieldContext a sus inputs hijos. Los inputs (MpsTextBox, MpsNumeric, MpsSelect, MpsDatePicker, MpsCheckBox) leen el cascade y propagan id, aria-describedby (apunta a hint o error) y aria-invalid automáticamente al input subyacente — sin código extra del consumer.

<MpsFormField Label="Email" Hint="Correo de trabajo" Error="@_emailError" Required="true">
    <MpsTextBox @bind-Value="_email" />
</MpsFormField>

Resultado renderizado (simplificado):

<div class="mps-field has-error">
    <label class="mps-label" for="mps-fld-abc123">Email <span aria-hidden="true">*</span></label>
    <div class="mps-field-control">
        <input id="mps-fld-abc123" aria-describedby="mps-fld-abc123-error" aria-invalid="true" ... />
    </div>
    <div class="mps-field-error" id="mps-fld-abc123-error" role="alert" aria-live="polite">
        @_emailError
    </div>
</div>

El error se anuncia automáticamente a screen readers vía role="alert" + aria-live="polite".

AriaLabel para controles sin label visible

Cuando un input vive fuera de un MpsFormField (ej. checkbox dentro de tabla, button icon-only, dialog con HeaderTemplate sin texto), pasá AriaLabel explícito:

<MpsCheckBox @bind-Checked="@row.Selected" AriaLabel="Seleccionar fila" />
<MpsButton AriaLabel="Cerrar" IconLeft="e-icons e-close" />
<MpsDialog @bind-Visible="_open" AriaLabel="Detalle del proyecto">
    <ChildContent>...</ChildContent>
</MpsDialog>

MpsDialog deriva aria-label desde Title automáticamente si no pasás AriaLabel.

0.4.1 retirado: tenía un bug donde el id auto-generado de MpsFormField se regeneraba en cada rerender del padre, rompiendo la conexión label[for]/aria-describedby. Si estás en 0.4.1, subí a 0.4.2 (fix incluido).

Inicialización del tema (recomendado)

Para que el accent del tema (default Emerald) se aplique desde el primer frame, el consumer debe inicializar ThemeService una vez en su layout principal:

@inject ThemeService Theme

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender) await Theme.InitializeAsync();
    }
}

Sin esto, los tokens accent quedan en su fallback estático (Direction A = emerald) hasta que el usuario cambie el tema desde la UI.

Cambio dinámico de acento

ThemeService permite cambiar el accent (y density / direction / radiusScale / fontFamily) en tiempo real desde cualquier página. La aplicación re-pinta instantáneamente — sidebar branded, KPI gauges, botones primary, focus rings, etc. — porque internamente el servicio reescribe las CSS custom properties (--color-accent-50--color-accent-700) en :root vía JS interop.

Acentos disponibles

public enum ThemeAccent { Cobalt, Indigo, Emerald, Amber, Slate }

5 paletas built-in. Cada acento expone una rampa completa de 9 stops (50/100/200/300/400/500/600/700/800/900) ya validada para contraste WCAG AA en texto y backgrounds. Emerald es el default si no override-as DefaultAccent.

Cambiar el acento desde una página

Patrón recomendado: MpsSegmented con un option por accent + binding al ThemeService.Accent.

@page "/configuracion"
@inject ThemeService Theme
@implements IDisposable

<MpsCard Title="Apariencia">
    <MpsFormField Label="Color del tema">
        <MpsSegmented TValue="ThemeAccent"
                      Value="@Theme.Accent"
                      ValueChanged="@(async v => await Theme.SetAsync(accent: v))"
                      Options="@_accentOpts" />
    </MpsFormField>

    <MpsFormField Label="Densidad">
        <MpsSegmented TValue="ThemeDensity"
                      Value="@Theme.Density"
                      ValueChanged="@(async v => await Theme.SetAsync(density: v))"
                      Options="@_densityOpts" />
    </MpsFormField>
</MpsCard>

@code {
    MpsSegmented<ThemeAccent>.Option[] _accentOpts = new[]
    {
        new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Cobalt,  "Cobalt"),
        new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Indigo,  "Indigo"),
        new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Emerald, "Emerald"),
        new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Amber,   "Amber"),
        new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Slate,   "Slate"),
    };

    MpsSegmented<ThemeDensity>.Option[] _densityOpts = new[]
    {
        new MpsSegmented<ThemeDensity>.Option(ThemeDensity.Compact,  "Compact"),
        new MpsSegmented<ThemeDensity>.Option(ThemeDensity.Comfort,  "Comfort"),
        new MpsSegmented<ThemeDensity>.Option(ThemeDensity.Spacious, "Spacious"),
    };

    protected override void OnInitialized() => Theme.Changed += StateHasChanged;
    public void Dispose() => Theme.Changed -= StateHasChanged;
}

Theme.Changed es un event Action? que se dispara después de aplicar el cambio. Suscribirse + StateHasChanged permite que la página refleje el Theme.Accent actualizado en bindings (Value= del segmented).

Cambio programático

// Cambiar solo accent:
await Theme.SetAsync(accent: ThemeAccent.Cobalt);

// Cambiar varios atributos en un solo apply:
await Theme.SetAsync(accent: ThemeAccent.Indigo, density: ThemeDensity.Compact);

// Volver al default del tenant:
await Theme.SetAsync(accent: ThemeAccent.Emerald, density: ThemeDensity.Comfort);

SetAsync es debounced de facto — múltiples await consecutivos son seguros, pero en UIs reactivas (sliders, color pickers continuos) sumá tu propio debounce client-side para evitar flicker.

Persistencia entre sesiones

Para que la elección del usuario sobreviva F5 y logout/login, registrá una implementación de IThemePersistence:

public interface IThemePersistence
{
    Task<ThemeState?> LoadAsync(CancellationToken ct);
    Task SaveAsync(ThemeState state, CancellationToken ct);
}
Opción A — ProtectedLocalStorage (Blazor Server, default)
public sealed class LocalStorageThemePersistence : IThemePersistence
{
    private readonly ProtectedLocalStorage _store;
    private const string Key = "mps.theme";

    public LocalStorageThemePersistence(ProtectedLocalStorage store) => _store = store;

    public async Task<ThemeState?> LoadAsync(CancellationToken ct)
    {
        try
        {
            var r = await _store.GetAsync<ThemeState>(Key);
            return r.Success ? r.Value : null;
        }
        catch { return null; }
    }

    public async Task SaveAsync(ThemeState state, CancellationToken ct)
        => await _store.SetAsync(Key, state);
}

// Program.cs
services.AddScoped<IThemePersistence, LocalStorageThemePersistence>();
Opción B — DB por usuario (multi-device)

Si tu app necesita que el tema viaje entre dispositivos, persistí ThemeState en una columna JSON sobre la tabla de usuarios:

public sealed class DbThemePersistence : IThemePersistence
{
    private readonly ICurrentUser _user;
    private readonly AppDbContext _db;

    public async Task<ThemeState?> LoadAsync(CancellationToken ct)
    {
        var u = await _db.Users.FindAsync([_user.Id], ct);
        return u?.ThemeJson is not null
            ? JsonSerializer.Deserialize<ThemeState>(u.ThemeJson)
            : null;
    }

    public async Task SaveAsync(ThemeState state, CancellationToken ct)
    {
        var u = await _db.Users.FindAsync([_user.Id], ct);
        if (u is null) return;
        u.ThemeJson = JsonSerializer.Serialize(state);
        await _db.SaveChangesAsync(ct);
    }
}

ThemeState es un record público con todos los atributos del tema:

public sealed record ThemeState(
    ThemeDirection Direction,
    ThemeAccent Accent,
    ThemeDensity Density,
    decimal RadiusScale,
    string FontFamily);

Reactividad en componentes custom del consumer

Para componentes custom que no usen CSS variables (ej. canvas/SVG con colores hardcodeados), suscribirse al evento Changed y recolorear:

@inject ThemeService Theme
@implements IDisposable

<canvas @ref="_canvas" width="400" height="200"></canvas>

@code {
    private ElementReference _canvas;

    protected override void OnInitialized() => Theme.Changed += OnThemeChanged;
    public void Dispose() => Theme.Changed -= OnThemeChanged;

    private async void OnThemeChanged()
    {
        // Theme.Accent cambió — recolorear el canvas custom.
        await JS.InvokeVoidAsync("myChart.recolor", _canvas, Theme.Accent.ToString());
        StateHasChanged();
    }
}

Tip: la mayoría de componentes MPS y tu UI consumer nativa no requieren esto — heredan el accent automáticamente vía CSS variables. Solo te preocupa esto si tenés rendering custom fuera del flujo CSS (canvas/WebGL/charts third-party que reciben colores como argumento, no como CSS).

Acento exclusivo de un componente (override puntual)

ThemeService.SetAsync(accent: ...) reescribe los --color-accent-* en :root — el cambio afecta toda la app. Si necesitás que solo el sidebar (o cualquier componente puntual) use un acento distinto al global, sobrescribí las custom properties en un selector más específico — las variables CSS cascadean y solo afectan al sub-árbol donde se redefinen.

<MpsSidebar Class="sb-corporate" Sections="@_sections" />

<style>
  .mps-sidebar.sb-corporate {
      --color-accent-50:  #EEF2FF;
      --color-accent-100: #E0E7FF;
      --color-accent-700: #4338CA;
  }
</style>

Botones, KPIs, focus rings y resto de la UI conservan el acento global. Solo cambia el highlight del item activo del sidebar marcado con Class="sb-corporate".

Variables que consume MpsSidebar (por impacto visual):

Variable Dónde se usa
--color-accent-50 Fondo del item activo (Tone Neutral/Tinted)
--color-accent-100 Borde del item activo + tinte de fondo Tone="Tinted"
--color-accent-700 Texto + iconos del item activo
--color-accent-600 Fondo de la sidebar en Tone="Branded"

Si solo querés cambiar el highlight sin migrar toda la rampa, podés ir más directo:

.mps-sidebar.sb-corporate .sb-item.active {
    background: #1F2937;
    color: #FFFFFF;
}

Default por tenant en Program.cs

Si tu app es multi-tenant y cada tenant tiene un default distinto:

services.AddMpsComponents(opts =>
{
    opts.DefaultAccent = currentTenant.BrandAccent;     // ej. ThemeAccent.Cobalt
    opts.DefaultDensity = ThemeDensity.Comfort;
    opts.DefaultFontFamily = "'Inter', system-ui, sans-serif";
});

El usuario siempre puede sobrescribir vía Theme.SetAsync — el default solo aplica en la primera carga si no hay state persistido.

Catálogo de componentes por categoría

Lista resumida — para sintaxis + props + casos completos ver /docs/components en el repo.

Buttons (MPS.Components.Design.Buttons)

Componente Versión Propósito
MpsButton 0.1.0 Botón base — wrapper de SfButton. Variants: Primary / Secondary / Ghost / Danger / Link. Sizes Sm / Md / Lg. Slots IconLeft / IconRight (clase CSS, ej. e-icons e-plus). AriaLabel para botones icon-only o texto no descriptivo. HtmlType="button" (default), "submit", "reset".
MpsIconButton 0.1.0 Botón cuadrado solo con icono. Required: Icon (clase CSS). Required best-practice: AriaLabel.
MpsButtonGroup 0.21.0 Toggle data-driven entre opciones (Items: IReadOnlyList<MpsButtonGroupItem> + @bind-Value). Reusa MpsButton.ButtonSize. Selected item recibe ButtonVariant.Primary.

v0.15.0 fix: tanto MpsButton como MpsIconButton enrutaban Type y aria-label como atributos sueltos al SfButton interno. En Syncfusion 33.2.3, SfButton declara HtmlAttributes con [Parameter(CaptureUnmatchedValues=true)] — cualquier atributo no reconocido choca con la asignación explícita y dispara InvalidOperationException al renderizar. Ahora ambos componentes consolidan los atributos HTML en el diccionario HtmlAttributes interno (incluyendo type y aria-label). Cero impacto en tu APIHtmlType y AriaLabel siguen siendo los mismos parámetros del consumer.

<MpsButton Variant="MpsButton.ButtonVariant.Primary"
           IconLeft="e-icons e-plus">Nuevo CA</MpsButton>

<MpsButton Variant="MpsButton.ButtonVariant.Ghost"
           IconLeft="e-icons e-export-excel">Exportar</MpsButton>

<MpsIconButton Icon="e-icons e-edit"
               AriaLabel="Editar fila"
               Size="MpsButton.ButtonSize.Sm" />

<MpsButton HtmlType="submit"
           Variant="MpsButton.ButtonVariant.Primary"
           Block="true">Guardar cambios</MpsButton>

Forms (MPS.Components.Design.Forms)

Componente Versión Propósito
MpsFormField 0.1.0 Wrapper label + hint + error.
MpsTextBox 0.1.0 Input texto (single/multi/password).
MpsNumeric 0.1.0 Numérico con stepper, min/max, format.
MpsSelect<TValue, TItem> 0.1.0 DropDown genérico.
MpsDatePicker 0.1.0 Selector de fecha.
MpsCheckBox 0.1.0 Checkbox 2-state / tristate.
MpsRadio<TValue> 0.1.0 Grupo radio genérico.
MpsToggle 0.1.0 Switch on/off.
WbsPicker 0.12.0 Selector jerárquico CA/WP con búsqueda + chip.
PeriodNavigator 0.12.0 < Apr-2026 > Day/Week/Month/Quarter/Year.
DateRangePicker 0.12.0 Rango con 8 presets + Custom + ExtraPresets.
CurrencyInput 0.12.0 Numeric + currency selector, default 0 decimales.
CodedTextbox 0.12.0 Auto-formato WBS/CBS uppercase + grouping.
SearchCombo<TItem> 0.12.0 Autocomplete genérico con debounce + match highlight.
MpsFileUploader 0.20.0 Drag-drop dropzone; emite IBrowserFile[]; validación MIME + tamaño; OnRejected.
MpsInputMask 0.20.0 Wrapper SfMaskedTextBox con presets RutChile/RucPeru/PhoneIntl.
MpsRangeSlider 0.22.0 Slider dual-handle numérico (wrapper SfSlider<double[]> Range). API decimal. Format/Prefix/Suffix en tooltips.
MpsColorPicker 0.22.0 Color picker free-form (Picker) o restringido a paleta (Palette). Wrapper SfColorPicker.

Data Display (MPS.Components.Design.DataDisplay)

MpsAvatar · MpsBadge · StatusChip · MpsCard · MpsStatCard · MpsMediaCard · MpsReviewCard · MpsActionCard · MpsEmpty · MpsProgress · MpsGrid<T> · MpsGridColumn · MpsGridActionColumn · MpsAutoGrid<T> · MpsAutoBadge + nuevos en 0.11.0/0.13.0/0.19.0/0.20.0: | Componente | Versión | Propósito | |---|---|---| | AttachmentList | 0.11.0 | Lista archivos + extension icons + preview/download/delete. | | BarChart | 0.13.0 | Wrapper SfChart Vertical/Horizontal con paleta MPS. | | DonutChart | 0.13.0 | SfAccumulationChart con InnerRadius + center label. | | StackedBarChart | 0.13.0 | N series stacked, modo Percent (StackingColumn100). | | UserChip | 0.13.0 | Avatar + nombre + rol compacto inline, 3 tamaños. | | MpsStatCard | 0.19.0 | Card de métricas inline con trend indicator. | | MpsMediaCard | 0.19.0 | Card con imagen (top cap o side). | | MpsReviewCard | 0.19.0 | Rating estrellas + autor. | | MpsActionCard | 0.19.0 | Header con persona + dropdown de acciones. | | MpsTimeline | 0.20.0 | Eventos cronológicos con dots por MpsTimelineStatus; orientación Vertical/Horizontal. | | MpsAvatarGroup | 0.20.0 | Stack horizontal de N avatares con overlap -8px; excedente colapsa a chip +N. | | MpsKanban<TItem> | 0.21.0 | Kanban genérico con selector functions (IdSelector, ColumnSelector) y drag-drop HTML5 native. OnItemMoved reporta MpsKanbanMove<TItem>. | | MpsRating | 0.21.0 | Rating estrellas standalone. Interactive (@bind-Value + hover preview) o read-only. 3 sizes (Sm/Md/Lg). | | MpsCounter | 0.21.0 | Número animado con easing cubic-out. Format/Prefix/Suffix/Culture configurables. Duration=0 = set inmediato. | | MpsTreeView<TNode> | 0.22.0 | Tree view genérico con selector functions (IdSelector, ParentSelector, LabelSelector, IconSelector). Wrapper SfTreeView. Single-select + ExpandAll. |

MpsBreadcrumbs · MpsPageHeader · MpsSegmented<TValue> · MpsSidebar (+ MpsSidebarSection + MpsSidebarItem + MpsSidebarUser) · MpsTabs · MpsAppHeader + nuevo: | Componente | Versión | Propósito | |---|---|---| | MpsStepper | 0.11.0 | Wizard multi-paso con CanAdvance/CanFinish + AllowJumpBack. | | MpsPagination | 0.22.0 | Page nav hand-rolled con ellipsis + SortedSet dedup. ShowSummary, ShowSizeSelector opcionales. |

MpsBreadcrumbs — resolución automática vía IRouteLabelsProvider

Registrar una única vez en DI y todos los breadcrumbs con AutoGenerate="true" resuelven label + icono automáticamente sin parámetros por página.

1 — Implementar en la app:

using MPS.Components.Design.Navigation;

public sealed class AppRouteLabelsProvider : IRouteLabelsProvider
{
    private static readonly Dictionary<string, RouteInfo> Routes =
        new(StringComparer.OrdinalIgnoreCase)
        {
            ["/"]                   = new("Inicio",           "e-icons e-home"),
            ["/proyectos"]          = new("Proyectos",        "e-icons e-gantt-chart"),
            ["/proyectos/overview"] = new("Vista General",    "e-icons e-eye"),
            ["/control-accounts"]   = new("Control Accounts", "e-icons e-hierarchy"),
            ["/riesgos"]            = new("Riesgos",          "e-icons e-warning"),
            ["/schedule"]           = new("Cronograma",       "e-icons e-schedule"),
            // Segmentos sueltos (fallback cuando el path completo no matchea)
            ["overview"]            = new("Vista General",    "e-icons e-eye"),
        };

    public RouteInfo? GetRoute(string pathOrSegment) =>
        Routes.TryGetValue(pathOrSegment, out var info) ? info : null;
}

2 — Registrar en Program.cs (una sola vez):

builder.Services.AddSingleton<IRouteLabelsProvider, AppRouteLabelsProvider>();

3 — Usar en cualquier página sin parámetros extra:

<MpsBreadcrumbs AutoGenerate="true" />

Prioridad de resolución por segmento: Items explícito → RouteLabels param → IRouteLabelsProvider DI → title-case del segmento.

RouteInfo(string Label, string? Icon)Icon acepta cualquier clase CSS de Syncfusion (ej. "e-icons e-home").
Si IRouteLabelsProvider no está registrado en DI, el componente lo ignora silenciosamente (sin excepción).

Overlays (MPS.Components.Design.Overlays)

MpsDialog · MpsDrawer · MpsPopover · MpsTooltip · MpsDropdownMenu · MpsCommandMenu + nuevo: | Componente | Versión | Propósito | |---|---|---| | ConfirmDialog | 0.11.0 | Wrapper Default/Danger/Warning sobre MpsDialog + IsBusy. |

Feedback (MPS.Components.Design.Feedback)

MpsAlert · MpsSkeleton · MpsToastHost (con ToastService) + nuevos en 0.13.0/0.20.0: | Componente | Versión | Propósito | |---|---|---| | Banner | 0.13.0 | Sticky top con 5 variants — Env purple gradient para QA/STAGING/DEV. | | EmptyStateIllustrated | 0.13.0 | 6 illustration kinds inline SVG + acción opcional. | | MpsAccordion | 0.20.0 | Contenedor colapsable template-based vía CascadingValue. Multiple permite varios abiertos. | | MpsAccordionItem | 0.20.0 | Sección hija de MpsAccordion. Disabled suprime click. Animación chevron + body via CSS. |

EVM / EPC (MPS.Components.Evm) — 42 componentes

P1 EVM/AACE (8): EvmKpiStrip · KpiCard · MpsGauge · MpsBulletGauge · CpiGauge · SpiGauge · CpiIndicator · SpiIndicator · VarianceIndicator · ForecastIndicator · EvmSemaforo · TrendChip · DoubleProgress · TripleProgress · EvmCashflowCurve · PhysicalProgressCurve · TcpiIndicator · EarnedScheduleIndicator · HealthDot · VariancePill · TrendDelta · MpsSparkline · CsiIndicator · EvmControlChart

P2 Cost Controls (7): MoneyDisplay · QuantityDisplay · CommitmentGauge · Waterfall · CashflowChart · ContingencyDrawdown · ManagementReserveTracker

Otros pre-roadmap: GanttChart · WbsTreeGrid · WbsKanban · R9cRow · R9cTable · RiskHeatmap · ChangeOrderLog · PurchaseOrderRegister · DeliverablesProgressTable · LookaheadGrid

Componente Versión Sección Propósito
MilestoneStrip 0.10.0 P3 Schedule Barra horizontal hitos + health + Hoy + critical rombo.
ResourceHistogram 0.10.0 P3 Schedule StackingColumn por categoría + capacity stripline.
CriticalPathSummary 0.10.0 P3 Schedule Widget ejecutivo float + critical + slip + next milestone.
ScheduleVarianceTable 0.10.0 P3 Schedule Tabla baseline/current/forecast + slip pill + indent WBS.
BurndownChart 0.10.0 P3 Schedule Plan dashed + Actual con markers + status burn rate.
RiskRegisterTable 0.9.0 P4 Risk Tabla PMI completa con filtros + búsqueda + drill-down.
TornadoChart 0.9.0 P4 Risk Sensitivity ranking low/high simétricas.
MonteCarloHistogram 0.9.0 P4 Risk Distribución QRA + percentile striplines (P50/P80/P90).
RiskBoard 0.9.0 P4 Risk Kanban PMI lifecycle.
RiskTrendLine 0.9.0 P4 Risk Sparkline burden total + TrendDelta LowerIsBetter.
ApprovalWorkflow 0.11.0 P5 Scope Cadena aprobadores con states + comments + overall computed.
RaciMatrix 0.11.0 P5 Scope Role × actividad + validación PMBoK 9.1.2.1 automática.
CommodityPriceChart 0.14.0 P8 Mining Cu/Au/Li temporal + baseline stripline + Δ% pill.
StockpileLevel 0.14.0 P8 Mining Barra horizontal + bandas operacionales + status auto.
ProductionCurve 0.14.0 P8 Mining Plan vs Actual + nameplate + status achievement %.
ShiftSchedule 0.14.0 P8 Mining Calendario semanal turnos Day/Night/Maint/Off/Custom.

Novedades v0.22.0 — Specialized Inputs

4 componentes nuevos:

  • MpsRangeSlider — slider dual-handle numérico (wrapper SfSlider Range).
  • MpsColorPicker — color picker free-form + palette (wrapper SfColorPicker).
  • MpsTreeView<TNode> — tree view genérico con selector functions (wrapper SfTreeView).
  • MpsPagination — page nav hand-rolled con ellipsis dedup.

Sin breaking changes.

Novedades v0.21.0 — Dashboards Polish

4 componentes nuevos:

  • MpsKanban<TItem> — kanban genérico con selector functions y drag-drop HTML5 native.
  • MpsRating — rating estrellas standalone (interactive / read-only).
  • MpsCounter — número animado con easing cubic-out + cancellation correcta.
  • MpsButtonGroup — toggle data-driven entre opciones.

3 improvements opt-in:

  • MpsProgress.Shape="Circular" con SVG ring.
  • MpsTabs.Orientation="Vertical" (mapea a Syncfusion).
  • MpsTooltip.ContentTemplate para markup rico.

Refactor interno: WbsKanban ahora wrappea MpsKanban<KanbanPackage> — API pública idéntica. Sin breaking changes.

Novedades v0.20.0 — Forms & Patterns

5 componentes nuevos:

  • MpsAccordion + MpsAccordionItem — secciones colapsables template-based con CascadingValue.
  • MpsTimeline — eventos cronológicos, vertical/horizontal con status-colored dots.
  • MpsFileUploader — drag-drop dropzone (sin HTTP); emite IBrowserFile[].
  • MpsInputMask — wrapper sobre SfMaskedTextBox con presets RUT/RUC/Phone.
  • MpsAvatarGroup — stack de N avatares con chip +N.

4 improvements:

  • MpsButton.Loading con spinner inline.
  • MpsBadge enum + Pill + Outline modifiers.
  • MpsAlert enum + Dismissible + OnDismiss.
  • MpsAvatar.Status overlay dot.

Breaking minor: MpsBadge.Variant y MpsAlert.Variant pasaron de string a enum (MpsBadgeVariant/MpsAlertVariant). Migración mecánica vía find/replace; VariantString [Obsolete] provee escape hatch hasta v1.0. Detalles en scripts/migrate-to-components-0.20.0.md.

Novedades v0.19.0 — Cards suite

  • MpsCard refactored — 6 variants semánticas (Default | Primary | Success | Warning | Danger | Info), 4 modos de acento (Bar | Border | Tinted | None), 2 densidades (Comfortable | Compact). Todos los params opt-in; defaults 100% backward-compat. Cards con Title muestran una barra vertical gris muy suave por default (Accent="Bar"); suprimir con Accent="MpsCardAccent.None".
  • 4 comportamientos opt-in: Collapsible (@bind-Open), Closable (OnClose), Fullscreen (ESC sin JS interop), Tabs (@bind-ActiveTabId + slot TabbedContent).
  • 4 componentes nuevos: MpsStatCard (métrica + trend inline), MpsMediaCard (image cap top/side), MpsReviewCard (rating estrellas + autor), MpsActionCard (header persona + dropdown).
  • Token palette --color-info-* en mps-tokens.css (Direction A + B). Resuelve referencias huérfanas pre-existentes en mps-evm-extras.css y mps-wbs-treegrid.css.
  • UserProfileCard refactorizado internamente para envolver <MpsCard NoPadding> — API pública idéntica.

Novedades v0.18.0

  • MpsIcons — clase estática (~150 constantes) con iconos Syncfusion agrupados por categoría semántica (Actions, Navigation, Export, Files, People, Status, Data, Charts, EVM, Arrows). Namespace MPS.Components.Design. Elimina strings hardcodeados y hace los usos refactorizables.
  • IRouteLabelsProvider + RouteInfo — interfaz DI para resolución automática de label + icono en MpsBreadcrumbs. Registrar AddSingleton<IRouteLabelsProvider, MyProvider>() una sola vez en Program.cs; todos los breadcrumbs con AutoGenerate="true" lo consumen automáticamente.
  • MpsPageHeader.Card — nuevo parámetro bool Card = true que envuelve el header en una card blanca con borde gris. Personalizable con CardBackground y CardBorderColor (valores CSS arbitrarios).
  • MpsDialog fixDialogEvents.OnOverlayClick renombrado a OnOverlayModalClick en Syncfusion 33.2.3. Corrige InvalidOperationException en runtime al renderizar cualquier modal.
  • SVGs eliminados — todos los iconos SVG inline de la librería (AppHeader, Sidebar, Breadcrumbs, Grid, AutoGrid, etc.) reemplazados por clases e-icons de Syncfusion — bundle más pequeño, consistencia visual con el design system.

Novedades v0.17.0 — RiskHeatmap v2

RiskHeatmap fue completamente rediseñado. Las celdas ahora muestran chips con el ID del riesgo (R-01, O-02) en lugar de un contador, tooltip rico con título/responsable/categoría/score al hover, overflow "+N más" con popover de lista completa, y un filtro segmentado Inherente / Residual / Objetivo. Las amenazas se distinguen en rojo y las oportunidades en verde.

Nuevos campos en RiskItem (todos nullable, backwards-compatible):

  • ProbabilityResidual, ImpactResidual — coordenadas de la vista Residual.
  • ProbabilityTarget, ImpactTarget — coordenadas de la vista Objetivo.
  • IsOpportunitytrue para oportunidades (chip verde).

Nuevos parámetros en RiskHeatmap:

  • View + ViewChanged — binding bidireccional (@bind-View).
  • ShowViewFilter — muestra/oculta el selector de vista (default true).

Migración entre versiones

Cada versión tiene su script de migración en scripts/migrate-to-components-X.Y.Z.md del repo. Para upgrades multi-versión (e.g. 0.4.x → 0.14.0) usar scripts/consumer-upgrade-to-0.10.0.md + el script de la versión target.

Para greenfield (proyecto Blazor WebApp nuevo): scripts/consumer-setup.md configura todo desde cero (Program.cs + App.razor + MainLayout).

Documentación completa

El catálogo vivo (demos + props detallados + ejemplos) está en github.com/Cayaqui/MPS_DESIGN:

  • /docs/components/*.md — sintaxis, ejemplos y tabla de props para cada componente.
  • /docs/roadmap-componentes.md — estado del roadmap (47 items, P1–P8 cerrado).
  • /CHANGELOG.md — historial de versiones con breaking changes y migration notes.
  • /scripts/migrate-to-components-*.md — scripts de migración por versión (0.5.0 → 0.14.0).
  • /docs/infrastructure/metadata.md — integración con Cayaqui.MPS.Metadata.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.34.0 41 5/8/2026
0.33.0 59 5/8/2026
0.32.0 40 5/8/2026
0.28.1 59 5/7/2026
0.28.0 33 5/7/2026
0.27.0 31 5/6/2026
0.26.0 52 5/6/2026
0.25.2 55 5/6/2026
0.25.1 49 5/6/2026
0.25.0 44 5/6/2026
0.24.1 45 5/6/2026
0.24.0 49 5/5/2026
0.23.1 56 5/5/2026
0.23.0 60 5/5/2026
0.22.1 57 5/5/2026
0.21.0 69 5/5/2026
0.20.0 63 5/5/2026
0.19.0 99 5/5/2026
0.18.4 111 5/4/2026
Loading failed

0.34.0 — Skeleton composites para arquetipos de loading-state. Cinco componentes nuevos sobre MpsSkeleton cubren los arquetipos comunes: MpsSkeletonStack (N líneas para forms/párrafos), MpsSkeletonCard (header + body línea/rect para detail/charts), MpsSkeletonRow (avatar + título + subtítulo para listas), MpsSkeletonGrid (grilla de tiles para KPI strips, default 1x4), MpsSkeletonTable (toolbar + N filas para index pages, RowHeight default 2.5rem para match con MpsAutoGrid). Composición típica de dashboard reemplaza un &lt;MpsSkeleton /&gt; plano por &lt;MpsSkeletonGrid /&gt; + &lt;MpsSkeletonCard Height="280px" /&gt; + &lt;MpsSkeletonTable /&gt; — reduce layout shift y comunica visualmente qué tipo de contenido viene. Todos los composites consumen internamente &lt;MpsSkeleton/&gt; — heredan shimmer y prefers-reduced-motion. +37 tests bUnit (798 total). Sin breaking changes — aditivos sobre v0.33.0. Ver scripts/migrate-to-components-0.34.0.md para recetas. v0.33.0 — MpsSkeleton runtime fix. El componente rendereaba `&lt;div class="mps-skeleton ..."&gt;` pero la regla CSS empacada era solo `.skeleton` — desde la primera versión. En 0.33.0 la clase se alinea (`.mps-skeleton`), `@keyframes shimmer` se namespacea como `mps-skeleton-shimmer`, se agregan defaults dimensionales por shape (line 100% × 0.875rem, rect 100% × 6rem, circle 2.5rem × 2.5rem) — `&lt;MpsSkeleton /&gt;` sin parámetros ahora es visible. Soporte `prefers-reduced-motion: reduce` (WCAG 2.3.3). +7 tests bUnit (761 total). Sin breaking changes; consumers que aplicaron hotfix consumer-side (regla `.mps-skeleton` en `app.css`) pueden removerlo. v0.32.0 — MpsPageHeader sincroniza el browser tab title automáticamente. Nuevo `ThemeOptions.AppName` (configurable en AddMpsComponents) sirve como sufijo. Formato `{Title} · {AppName}` (middot). Override por instancia con `BrowserTabSuffix` (sufijo) y `BrowserTabTitle` (título distinto al header). Opt-out con `SetBrowserTab=false`. Helper estático `MpsPageHeader.ComputeBrowserTabTitle(...)` testeable. Fix de bug: la versión anterior tenía `<PageTitle>@Title - MOVES</PageTitle>` hardcodeado en una librería genérica — rompía consumers no-MOVES y filtraba el nombre del proyecto. DoubleProgress / TripleProgress: barras a 90% opacidad (antes 50%). +12 tests (754 total). Sin breaking de API; el browser tab default sigue activo (ahora correcto). v0.31.0 — CashflowChart leyenda con valores en CutOffDate. Reemplaza el header "totales" por una leyenda HTML con dos grupos: "Mes de cutoff" (Plan/Real/Forecast columnas — valor del mes de control, Forecast=total restante) y "Acumulado al cutoff" (Plan acum./Real acum./Forecast acum.=EAC). Si no hay CutOffDate, los valores son los totales del rango. Cada entrada con swatch que replica el estilo del chart (columnas opacidad 0.65, líneas sólidas, Forecast acum. dashed verde). Legend Syncfusion built-in se oculta — la nueva leyenda HTML usa MoneyDisplay para formato consistente con el resto del design system. Nuevo helper estático `ComputeLegendValues(raw, cutOffDate)` testeable. +3 tests (742 total). Sin breaking de API. v0.30.1 — CashflowChart palette EVM canónica. Plan=azul (#2E5BFF), Real=rojo (#D92D20), Forecast=verde (#039855) con la línea acumulada de Forecast en verde segmentado (DashArray 6,3). Las columnas mensuales usan los mismos hex al 75% de opacidad para diferenciarse visualmente de las líneas acumuladas (100%). Reemplaza la paleta inicial 0.30.0 (Plan azul, Real verde, Forecast naranja).

v0.30.0 — CashflowChart 2.0: extiende el componente con capa acumulada (3 series tipo línea sobre eje Y secundario) además de las columnas mensuales. Plan acum. cubre todo el rango; Real acum. ≤ CutOffDate; Forecast acum. arranca en el Real acum. del cutoff (continuidad visual con la línea Real) y luego suma los Forecast period-by-period. Las columnas Forecast quedan filtradas automáticamente a meses &gt; CutOffDate (antes el caller debía nullear los pre-cutoff manualmente). Nuevo parámetro `ShowCumulative` (default `true`) controla la capa de líneas. Nuevo parámetro `CutOffDate` (alias retro-compat de `ControlDate`). Anchor robusto cuando CutOffDate no coincide con un punto exacto del dataset (cae en el último ≤ cutoff). +10 tests (739 total). Sin breaking changes; consumers que pasen `ShowCumulative="false"` mantienen el aspecto 0.29.x.

v0.29.0 — Tipografía dual MPS. Roboto Condensed para charts, tablas, valores numéricos, KPIs y códigos tabulares (Money/Counter/Range/Stat-card/Pagination/R9C/PO/Risk/Sensitivity/Lookahead). Inter para cuerpo, labels, títulos, botones — "el resto del contenido". Nuevos tokens CSS: --font-numeric, --font-table, --font-chart. Charts Syncfusion (SfChart, AccumulationChart, StockChart, RangeNavigator, Sparkline, Gauges, Gantt) y custom (mps-gauge, mps-bullet) reciben Roboto Condensed via SVG &lt;text&gt; rule. SfGrid + TreeGrid + .mps-table + .mps-po-table + .mps-deliv-table + .mps-risk-table + .mps-svt-table heredan font-table. Utility `.mono` re-apuntada a --font-numeric (los 30+ usos en razor — MoneyDisplay/MpsCounter/CurrencyInput/MpsGridColumn/etc — quedan correctos sin migración). Nuevo `.kbd` para mono real (JetBrains Mono kept). El @import del Google Fonts ahora trae Inter 300/400/500/600/700 + Roboto Condensed 300/400/500/700 + JetBrains Mono. Recomendado: el consumer precarga las fuentes con &lt;link rel="preconnect"&gt; en App.razor para LCP óptimo (ver PackageReadme). v0.28.1 — Patch fix de IOptions&lt;ThemeOptions&gt; en AddMpsComponents. v0.28.0 — Theme SSR support, elimina FOUC. Sin breaking changes en v0.29.0; sólo cambia la familia tipográfica visual.