Files
pireon-ui/apps/playground/src/app/pages/catalog/catalog.page.html
2026-07-30 21:20:00 +02:00

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>