629 lines
19 KiB
HTML
629 lines
19 KiB
HTML
<span class="event">Último evento: {{ lastEvent() }}</span>
|
|
|
|
<main>
|
|
<div class="col">
|
|
<app-section
|
|
title="Formularios (Signal Forms)"
|
|
hint="Cada campo se enlaza con [formField]; la validación vive en el form()."
|
|
>
|
|
<div class="stack">
|
|
<pi-text-field [formField]="f.name">
|
|
<pi-label>Nombre</pi-label>
|
|
</pi-text-field>
|
|
|
|
<pi-email-field [formField]="f.email">
|
|
<pi-label>Email</pi-label>
|
|
</pi-email-field>
|
|
|
|
<pi-password-field [formField]="f.password">
|
|
<pi-label>Contraseña</pi-label>
|
|
</pi-password-field>
|
|
|
|
<pi-textarea [formField]="f.bio">
|
|
<pi-label>Biografía</pi-label>
|
|
</pi-textarea>
|
|
|
|
<pi-incremental-number-field [formField]="f.age">
|
|
<pi-label>Edad</pi-label>
|
|
</pi-incremental-number-field>
|
|
|
|
<pi-currency-field [formField]="f.price">
|
|
<pi-label>Precio</pi-label>
|
|
</pi-currency-field>
|
|
|
|
<pi-incremental-range-field
|
|
[formField]="f.range"
|
|
[minValue]="0"
|
|
[maxValue]="100"
|
|
unit=""
|
|
[step]="1"
|
|
/>
|
|
|
|
<pi-search-field [formField]="f.search" placeholder="Buscar…" />
|
|
|
|
<pi-icon-field [formField]="f.website" icon="globe">
|
|
<pi-label>Web</pi-label>
|
|
</pi-icon-field>
|
|
|
|
<pi-phone-field [formField]="f.phone">
|
|
<pi-label>Teléfono</pi-label>
|
|
</pi-phone-field>
|
|
|
|
<pi-number-field [formField]="f.weight">
|
|
<pi-label>Peso (kg)</pi-label>
|
|
</pi-number-field>
|
|
|
|
<label class="inline">
|
|
<span>Newsletter</span>
|
|
<pi-slide-toggle [formField]="f.newsletter" />
|
|
</label>
|
|
|
|
<pi-slider
|
|
[formField]="f.volume"
|
|
unit="%"
|
|
label="Volumen"
|
|
[maxValue]="100"
|
|
/>
|
|
|
|
<div class="range-slider-caption">
|
|
<span
|
|
>Rango de precio (pi-range-slider no pinta su propia etiqueta)</span
|
|
>
|
|
<pi-range-slider
|
|
[formField]="f.priceRange"
|
|
unit="€"
|
|
[minValue]="0"
|
|
[maxValue]="100"
|
|
/>
|
|
</div>
|
|
|
|
<pi-date-field [formField]="f.birthDate">
|
|
<pi-label>Fecha de nacimiento</pi-label>
|
|
</pi-date-field>
|
|
|
|
<pi-select-field [formField]="f.language" [options]="languages">
|
|
<pi-label>Idioma</pi-label>
|
|
</pi-select-field>
|
|
|
|
<pi-searchable-select-field
|
|
[formField]="f.country2"
|
|
[options]="countryOptions"
|
|
>
|
|
<pi-label>País</pi-label>
|
|
</pi-searchable-select-field>
|
|
|
|
<pi-searchable-multi-select-field
|
|
[formField]="f.visited"
|
|
[options]="countryOptions"
|
|
>
|
|
<pi-label>Países visitados</pi-label>
|
|
</pi-searchable-multi-select-field>
|
|
|
|
<pi-multi-select-field [formField]="f.skills" [options]="skillOptions">
|
|
<pi-label>Conocimientos</pi-label>
|
|
</pi-multi-select-field>
|
|
|
|
<pi-radio-group
|
|
[formField]="f.shipping"
|
|
[options]="shippingOptions"
|
|
[columns]="1"
|
|
/>
|
|
|
|
<pi-checkbox [formField]="f.terms" label="Acepto los términos" />
|
|
|
|
<pi-file-upload
|
|
[formField]="f.attachments"
|
|
[multiple]="true"
|
|
accept="image/*,.pdf"
|
|
[maxSizeBytes]="2097152"
|
|
[maxFiles]="3"
|
|
label="Arrastra ficheros o pulsa para elegir"
|
|
hint="Imágenes o PDF · máx. 2 MB · hasta 3"
|
|
(rejected)="log('rechazados: ' + $event.length)"
|
|
/>
|
|
|
|
<button pi-primary w100 (click)="submitForm()">
|
|
Enviar (muestra los errores)
|
|
</button>
|
|
</div>
|
|
</app-section>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<app-section title="Estado del formulario" hint="Modelo y validez en vivo.">
|
|
<div class="stack full">
|
|
<p class="state">
|
|
<strong>válido:</strong> {{ f().valid() }} ·
|
|
<strong>tocado:</strong> {{ f().touched() }} ·
|
|
<strong>modificado:</strong> {{ f().dirty() }}
|
|
</p>
|
|
<pre>{{ modelJson() }}</pre>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Botones">
|
|
<button pi-primary large (click)="log('botón grande')">Primario</button>
|
|
<button pi-ghost small (click)="log('botón ghost')">Ghost</button>
|
|
<button pi-outline (click)="log('botón outline')">Outline</button>
|
|
<button pi-primary w100 (click)="log('botón w100')">
|
|
Ancho completo
|
|
</button>
|
|
<pi-action-button text="Acción" (execute)="log('action-button')" />
|
|
<pi-icon-button icon="add" text="Añadir" (execute)="log('icon-button')" />
|
|
<pi-chevron-button (toggle)="log('chevron')" />
|
|
<pi-close-button (toggle)="log('close')" />
|
|
<pi-hamburger (toggle)="log('hamburger')" />
|
|
<pi-radio-button
|
|
[isActive]="radioActive()"
|
|
[isDisabled]="false"
|
|
(execute)="radioActive.set(!radioActive())"
|
|
/>
|
|
<pi-switch-button
|
|
[content]="['Mensual', 'Anual']"
|
|
contentType="text"
|
|
[(isActive)]="switchActive"
|
|
(toggledTo)="log('switch ' + $event)"
|
|
/>
|
|
<pi-toggle-button
|
|
text="Favorito"
|
|
icon="star-fill"
|
|
[isActive]="toggleActive()"
|
|
[isDisabled]="false"
|
|
(execute)="toggleActive.set(!toggleActive())"
|
|
/>
|
|
</app-section>
|
|
|
|
<app-section title="Iconos y texto">
|
|
<pi-icon icon="add" color="primary" size="large" />
|
|
<pi-icon icon="search" color="accent" size="large" />
|
|
<pi-icon icon="close" color="warn" size="large" />
|
|
<pi-icon
|
|
icon="check"
|
|
color="primary"
|
|
appearance="fill"
|
|
[isStroked]="true"
|
|
/>
|
|
<pi-icon
|
|
icon="info"
|
|
color="primary"
|
|
size="large"
|
|
pi-tooltip
|
|
tooltipText="Esto usa PiTooltipDirective"
|
|
tooltipPosition="above"
|
|
/>
|
|
<pi-text-viewer value="Texto con el visor" fontSize="m" color="black" />
|
|
<pi-divider color="primary" width="large" label="Separador" />
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Cabecera y listado"
|
|
hint="pi-headline para portadas de sección, pi-list-item para filas de listado. pi-headline resuelve pathImg contra un CDN externo fijo, así que la ilustración solo carga con red."
|
|
>
|
|
<div class="stack full">
|
|
<pi-headline pathImg="404.svg" alt="Ilustración">
|
|
<pi-headline-label-content>
|
|
<h4>Bienvenido</h4>
|
|
</pi-headline-label-content>
|
|
<pi-headline-description-content>
|
|
<p>Gestiona tu cuenta desde aquí.</p>
|
|
</pi-headline-description-content>
|
|
</pi-headline>
|
|
|
|
<pi-list-item borderType="bottom" color="white" [isClickable]="true">
|
|
<pi-list-item-left-icon>
|
|
<pi-icon icon="user" color="primary" />
|
|
</pi-list-item-left-icon>
|
|
<pi-list-item-left-content>
|
|
<span>Perfil</span>
|
|
</pi-list-item-left-content>
|
|
<pi-list-item-right-icon>
|
|
<pi-icon icon="chevron-forward" color="grey" />
|
|
</pi-list-item-right-icon>
|
|
</pi-list-item>
|
|
|
|
<pi-list-item borderType="bottom" color="white" [isClickable]="true">
|
|
<pi-list-item-left-icon>
|
|
<pi-icon icon="notification-on" color="primary" />
|
|
</pi-list-item-left-icon>
|
|
<pi-list-item-left-content>
|
|
<span>Notificaciones</span>
|
|
</pi-list-item-left-content>
|
|
<pi-list-item-right-content>
|
|
<pi-chip-status status="pending" label="3" />
|
|
</pi-list-item-right-content>
|
|
</pi-list-item>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Chips">
|
|
<pi-chip label="Etiqueta" color="primary" />
|
|
<pi-chip-status status="success" label="Correcto" />
|
|
<pi-chip-status status="error" label="Error" />
|
|
<pi-chip-status status="pending" label="Pendiente" />
|
|
<pi-chip-value [value]="42" unit="%" label="Progreso" />
|
|
<pi-chip-action
|
|
label="Acción"
|
|
[isActive]="true"
|
|
(execute)="log('chip-action')"
|
|
/>
|
|
<pi-chip-time
|
|
date="2026-07-28"
|
|
time="2026-07-28T09:30:00Z"
|
|
[isActive]="false"
|
|
/>
|
|
<pi-chip-date date="2026-07-28" [isActive]="false" />
|
|
<pi-tile label="Ingresos" value="12.4K €" icon="coin" />
|
|
</app-section>
|
|
|
|
<app-section title="Card">
|
|
<pi-card
|
|
alignment="flex-start"
|
|
gap="m"
|
|
innerPadding="l"
|
|
context="public"
|
|
[isClickable]="true"
|
|
(execute)="log('card')"
|
|
>
|
|
<pi-card-top-content>
|
|
<h4>Título de la tarjeta</h4>
|
|
</pi-card-top-content>
|
|
<pi-card-center-content>
|
|
<p>Contenido de ejemplo para pi-card.</p>
|
|
</pi-card-center-content>
|
|
</pi-card>
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Panel"
|
|
hint="pi-expansion-panel[type=form] usa Reactive Forms clásico, no Signal Forms."
|
|
>
|
|
<div class="stack full">
|
|
<pi-panel
|
|
title="Resumen"
|
|
subtitle="Últimos 30 días"
|
|
height="medium"
|
|
outline="grey"
|
|
status=""
|
|
innerPadding="l"
|
|
>
|
|
<pi-panel-center-content>
|
|
<p>Contenido central del panel.</p>
|
|
</pi-panel-center-content>
|
|
<pi-panel-bottom-content>
|
|
<button pi-ghost (click)="log('panel bottom action')">
|
|
Ver más
|
|
</button>
|
|
</pi-panel-bottom-content>
|
|
</pi-panel>
|
|
|
|
<pi-expansion-panel
|
|
type="form"
|
|
title="Datos de contacto"
|
|
height="small"
|
|
outline="grey"
|
|
status=""
|
|
innerPadding="l"
|
|
[(isCollapsed)]="isPanelCollapsed"
|
|
(edit)="log('expansion-panel edit')"
|
|
>
|
|
<pi-panel-center-content>
|
|
<p>{{ f.contactEmail().value() || 'Sin email' }}</p>
|
|
</pi-panel-center-content>
|
|
<pi-panel-expandable-content>
|
|
<input
|
|
[formField]="f.contactEmail"
|
|
placeholder="email@ejemplo.com"
|
|
/>
|
|
</pi-panel-expandable-content>
|
|
</pi-expansion-panel>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Accordion">
|
|
<div class="stack full">
|
|
<pi-accordion
|
|
color="primary"
|
|
appearance="outline"
|
|
[(expandedItemIndex)]="expandedAccordionIndex"
|
|
>
|
|
<pi-accordion-item
|
|
title="Facturación"
|
|
subtitle="Datos de pago"
|
|
status="info"
|
|
statusLabel="Pendiente"
|
|
>
|
|
<ng-template pi-accordion-item-expandable-content>
|
|
<p>Contenido expandible del ítem 1.</p>
|
|
</ng-template>
|
|
</pi-accordion-item>
|
|
<pi-accordion-item
|
|
title="Envío"
|
|
status="success"
|
|
statusLabel="Completo"
|
|
>
|
|
<ng-template pi-accordion-item-expandable-content>
|
|
<p>Detalles de envío.</p>
|
|
</ng-template>
|
|
</pi-accordion-item>
|
|
</pi-accordion>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Tab">
|
|
<div class="stack full">
|
|
<pi-tab
|
|
[tabButtons]="tabs"
|
|
[isSmallDevice]="false"
|
|
(tabChange)="log('tab ' + $event)"
|
|
>
|
|
<ng-template pi-tab-content
|
|
>Contenido de la pestaña General.</ng-template
|
|
>
|
|
<ng-template pi-tab-content
|
|
>Contenido de la pestaña Facturación.</ng-template
|
|
>
|
|
<ng-template pi-tab-content
|
|
>Contenido de la pestaña Avanzado.</ng-template
|
|
>
|
|
</pi-tab>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Banner">
|
|
<div class="stack full">
|
|
<pi-banner
|
|
mode="dark"
|
|
title="Nueva funcionalidad"
|
|
message="Descubre el nuevo panel de control."
|
|
buttonText="Ver más"
|
|
pathImg="404.svg"
|
|
extension="svg"
|
|
(execute)="log('banner')"
|
|
/>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Note">
|
|
<div class="stack full">
|
|
<pi-note
|
|
title="Aviso"
|
|
text="Tu suscripción caduca pronto."
|
|
status="pending"
|
|
gap="s"
|
|
>
|
|
<pi-note-action-content>
|
|
<button pi-ghost (click)="log('note action')">Renovar</button>
|
|
</pi-note-action-content>
|
|
</pi-note>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Select"
|
|
hint="pi-select* usa Signal Forms como el resto: [formField] enlaza el valor completo de la opción elegida. #optionTemplate/#selectedTemplate son content children obligatorios de la librería; sin ellos, Angular lanza NG0951."
|
|
>
|
|
<div class="stack full">
|
|
<pi-select
|
|
[formField]="f.country"
|
|
keyIdProperty="id"
|
|
label="País"
|
|
placeholder="Selecciona un país"
|
|
[options]="countries"
|
|
>
|
|
<ng-template #optionTemplate let-option>
|
|
<pi-select-option [id]="option.id" [label]="option.name" />
|
|
</ng-template>
|
|
<ng-template #selectedTemplate></ng-template>
|
|
</pi-select>
|
|
|
|
<pi-select-multiple
|
|
[formField]="f.tags"
|
|
keyIdProperty="id"
|
|
label="Etiquetas"
|
|
placeholder="Selecciona etiquetas"
|
|
[options]="tags"
|
|
[hasSelectAllOptions]="true"
|
|
selectAllLabel="Todas"
|
|
>
|
|
<ng-template #optionTemplate let-option>
|
|
<pi-select-option
|
|
type="multiple"
|
|
[id]="option.id"
|
|
[label]="option.name"
|
|
/>
|
|
</ng-template>
|
|
<ng-template #selectedTemplate></ng-template>
|
|
</pi-select-multiple>
|
|
|
|
<pi-select-period
|
|
[formField]="f.period"
|
|
keyIdProperty="id"
|
|
label="Periodo"
|
|
placeholder="Selecciona un periodo"
|
|
[options]="countries"
|
|
>
|
|
<ng-template #optionTemplate let-option>
|
|
<pi-select-option [id]="option.id" [label]="option.name" />
|
|
</ng-template>
|
|
<ng-template #selectedTemplate></ng-template>
|
|
</pi-select-period>
|
|
|
|
<pi-select-carousel
|
|
[formField]="f.plan"
|
|
keyIdProperty="id"
|
|
[options]="plans"
|
|
[itemsDesktop]="3"
|
|
[device]="device"
|
|
>
|
|
<ng-template #optionTemplate let-option>
|
|
<pi-select-option
|
|
[id]="option.id"
|
|
[label]="option.name"
|
|
[description]="option.description"
|
|
flexDirection="column"
|
|
/>
|
|
</ng-template>
|
|
<ng-template #selectedTemplate></ng-template>
|
|
</pi-select-carousel>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Modal y notificaciones"
|
|
hint="pi-modal y pi-snack-bar no se usan como etiquetas: se abren desde TypeScript vía MatDialog y NotificationService."
|
|
>
|
|
<button pi-primary (click)="openDemoModal()">Abrir modal</button>
|
|
<button pi-outline (click)="showSuccessNotification()">
|
|
Notificación de éxito
|
|
</button>
|
|
<button pi-outline (click)="showErrorNotification()">
|
|
Notificación de error
|
|
</button>
|
|
</app-section>
|
|
|
|
<app-section title="Data grid">
|
|
<div class="stack full">
|
|
<pi-data-grid
|
|
[data]="employees"
|
|
[columns]="3"
|
|
[rows]="1"
|
|
[index]="0"
|
|
[isActive]="false"
|
|
mode="list"
|
|
dataDirection="column"
|
|
(clickItem)="log('data-grid item')"
|
|
>
|
|
<ng-template #valuesTemplate let-value let-i="index">
|
|
<span pi-cell [index]="i">{{ value.name }} — {{ value.role }}</span>
|
|
</ng-template>
|
|
</pi-data-grid>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Data table">
|
|
<div class="stack full">
|
|
<pi-base-data-table dataDirection="row" type="header">
|
|
<pi-data-table-item
|
|
heading="Concepto"
|
|
[values]="['Cliente', 'Importe', 'Fecha']"
|
|
>
|
|
<ng-template #item let-value="value">
|
|
<pi-cell-type type="text" [display]="value" />
|
|
</ng-template>
|
|
</pi-data-table-item>
|
|
</pi-base-data-table>
|
|
|
|
@for (row of invoiceRows; track row) {
|
|
<pi-base-data-table dataDirection="row" type="data">
|
|
<pi-data-table-item [values]="row">
|
|
<ng-template #item let-value="value" let-i="index">
|
|
<pi-cell-type
|
|
[type]="i === 1 ? 'amount' : 'text'"
|
|
[display]="value"
|
|
/>
|
|
</ng-template>
|
|
</pi-data-table-item>
|
|
</pi-base-data-table>
|
|
}
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Chart">
|
|
<div class="stack full">
|
|
<pi-chart
|
|
type="line"
|
|
[device]="device"
|
|
[xAxisMarks]="['Ene', 'Feb', 'Mar', 'Abr']"
|
|
[datasets]="chartDatasets"
|
|
/>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section title="Carousel">
|
|
<div class="stack full">
|
|
<pi-carousel
|
|
[itemsDesktop]="3"
|
|
[itemsTablet]="2"
|
|
[itemsMobile]="1"
|
|
[hideSwipeButtons]="false"
|
|
[device]="device"
|
|
color="primary"
|
|
(itemChanged)="log('carousel ' + $event)"
|
|
>
|
|
@for (item of carouselItems; track item.id) {
|
|
<ng-template pi-carousel-item>
|
|
<div class="demo-card">{{ item.label }}</div>
|
|
</ng-template>
|
|
}
|
|
</pi-carousel>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Stepper"
|
|
hint="Cubre WrapperTypeClass.Steps, que estaba declarado sin componente ni CSS."
|
|
>
|
|
<div class="stack full">
|
|
<pi-stepper
|
|
#stepper
|
|
[steps]="steps"
|
|
[(selectedIndex)]="stepperIndex"
|
|
(stepChange)="log('paso ' + $event)"
|
|
/>
|
|
<div class="inline">
|
|
<button pi-ghost small (click)="stepper.previous()">Anterior</button>
|
|
<button pi-ghost small (click)="stepper.next()">Siguiente</button>
|
|
</div>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Dynamic menu"
|
|
hint="ComponentFamilyClass ya declaraba la familia dynamic-menu y util/menu traía la clase base; faltaba el componente."
|
|
>
|
|
<div class="stack full">
|
|
<pi-dynamic-menu
|
|
label="Navegación"
|
|
[menuItems]="menuEntries"
|
|
(selectEntry)="log('menú: ' + $any($event).label)"
|
|
/>
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Skeleton"
|
|
hint="Resuelve el TODO que quedaba en pi-data-grid; también es el respaldo cuando no se pasa plantilla."
|
|
>
|
|
<div class="stack full">
|
|
<pi-skeleton [lines]="3" />
|
|
<pi-skeleton shape="block" width="100%" height="4rem" />
|
|
<pi-skeleton shape="circle" width="3rem" />
|
|
</div>
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Feedback y navegación"
|
|
hint="pi-spinner se omite a propósito: es un overlay position:fixed de página completa, no un indicador en línea."
|
|
>
|
|
<pi-progress-spinner label="Progreso" [value]="65" [diameter]="72" />
|
|
<pi-progress-bar label="Carga" [value]="65" />
|
|
<pi-paginator
|
|
[(currentPage)]="currentPage"
|
|
[pageSize]="10"
|
|
[totalPages]="5"
|
|
[isDisabled]="false"
|
|
(changePage)="log('página ' + $event)"
|
|
/>
|
|
</app-section>
|
|
|
|
<app-section
|
|
title="Página no encontrada"
|
|
hint="pi-not-found: página completa (pi-headline + botón volver), no un widget en línea."
|
|
>
|
|
<div class="not-found-frame">
|
|
<pi-not-found />
|
|
</div>
|
|
</app-section>
|
|
</div>
|
|
</main>
|