prepara update

This commit is contained in:
2026-07-31 20:47:15 +02:00
parent a0908e1b5a
commit b02c885b83
74 changed files with 1379 additions and 883 deletions

View File

@@ -6,7 +6,6 @@ import {
} from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter, withInMemoryScrolling } from '@angular/router';
import { provideStore } from '@ngrx/store';
import { routes } from './app.routes';
import {
@@ -16,7 +15,7 @@ import {
} from '@ngx-translate/core';
import { Observable, of } from 'rxjs';
import { IconRegistryService, deviceConfigsReducer, featureKey } from '@pi/ui';
import { DeviceConfigsService, IconRegistryService } from '@pi/ui';
/**
* Diccionario en línea: la librería resuelve los textos con `| translate`, y
@@ -62,12 +61,15 @@ export const appConfig: ApplicationConfig = {
// Los iconos son SVG registrados en el MatIconRegistry (assets/icons).
provideAppInitializer(() => inject(IconRegistryService).init()),
// data-table, carousel y background-image leen el tipo de dispositivo;
// instanciarlo arranca el listener de breakpoints.
provideAppInitializer(() => {
inject(DeviceConfigsService);
}),
provideTranslateService({
fallbackLang: 'es',
loader: { provide: TranslateLoader, useClass: PlaygroundTranslateLoader },
}),
// data-table y background-image leen el tipo de dispositivo del store.
provideStore({ [featureKey]: deviceConfigsReducer }),
],
};

View File

@@ -9,10 +9,19 @@
</div>
<div class="theme-toggles">
<label class="theme-toggle">
<span>Tema alternativo</span>
<pi-slide-toggle [(checked)]="alternativeTheme" />
</label>
<div class="theme-switcher" role="radiogroup" aria-label="Tema">
@for (option of themeOptions; track option.value) {
<button
type="button"
role="radio"
[attr.aria-checked]="theme() === option.value"
[class.is-active]="theme() === option.value"
(click)="theme.set(option.value)"
>
{{ option.label }}
</button>
}
</div>
<label class="theme-toggle">
<span>Modo oscuro</span>
<pi-slide-toggle [(checked)]="darkMode" />
@@ -32,16 +41,14 @@
</nav>
<p class="theme-hint">
Tema:
<strong>{{ alternativeTheme() ? 'alternative' : 'default' }}</strong> ·
<code
>{{ alternativeTheme() ? 'pi-shell__alternative' : 'pi-shell__default'
}}</code
>
en el <code>&lt;body&gt;</code>. Los dos temas comparten exactamente los
Tema: <strong>{{ theme() }}</strong> ·
<code>{{ shellClassByTheme[theme()] }}</code>
en el <code>&lt;body&gt;</code>. Los cuatro temas comparten exactamente los
mismos selectores: solo cambian los tokens <code>--pi-color-*</code> y
<code>--pi-theme-*</code> que definen <code>themes/_default.scss</code> y
<code>themes/_alternative.scss</code>.
<code>--pi-theme-*</code> que definen <code>themes/_default.scss</code>,
<code>themes/_alternative.scss</code>, <code>themes/_minimal.scss</code> y
<code>themes/_gradient.scss</code>. Prueba "Gradient" y enfoca un campo del
formulario.
</p>
</header>

View File

@@ -47,6 +47,30 @@
white-space: nowrap;
}
.theme-switcher {
display: flex;
gap: 0.25rem;
padding: 0.2rem;
border: 1px solid var(--pg-card-border);
border-radius: 8px;
button {
padding: 0.3rem 0.75rem;
border: none;
border-radius: 6px;
background: transparent;
color: var(--pg-text-muted);
font-size: 0.8rem;
cursor: pointer;
&.is-active {
background: var(--pg-badge-bg);
color: var(--pg-badge-text);
font-weight: 600;
}
}
}
.theme-hint {
margin: 0.75rem 0 0;
font-size: 0.8rem;

View File

@@ -33,13 +33,30 @@ export class App {
protected readonly darkMode = signal(false);
protected readonly themeOptions: ReadonlyArray<{
value: ThemeVariant;
label: string;
}> = [
{ value: 'default', label: 'Default' },
{ value: 'alternative', label: 'Alternative' },
{ value: 'minimal', label: 'Minimal' },
{ value: 'gradient', label: 'Gradient' },
];
/**
* Tema visual de la librería. `ShellClass` devuelve las dos clases que hay
* que poner juntas (`pi-shell pi-shell__<tema>`); van en el `<body>` porque
* los overlays de Material (diálogos, snackbars) se montan fuera de
* `<app-root>` y también deben heredarlas.
*/
protected readonly alternativeTheme = signal(false);
protected readonly theme = signal<ThemeVariant>('default');
protected readonly shellClassByTheme: Record<ThemeVariant, string> = {
default: ShellClass.Default,
alternative: ShellClass.Alternative,
minimal: ShellClass.Minimal,
gradient: ShellClass.Gradient,
};
constructor() {
effect(() => {
@@ -50,10 +67,9 @@ export class App {
});
effect(() => {
const shell = this.alternativeTheme()
? ShellClass.Alternative
: ShellClass.Default;
this.document.body.className = shell;
this.document.body.className = this.shellClassByTheme[this.theme()];
});
}
}
type ThemeVariant = 'default' | 'alternative' | 'minimal' | 'gradient';

View File

@@ -195,6 +195,26 @@
<pi-divider color="primary" width="large" label="Separador" />
</app-section>
<app-section
title="Avatar y Badge"
hint="pi-avatar cae en cascada a iniciales (con name) y luego a icono; pi-badge envuelve cualquier contenido proyectado con un indicador superpuesto."
>
<pi-avatar image="https://i.pravatar.cc/128?img=12" alt="Ada Lovelace" />
<pi-avatar name="Ada Lovelace" color="primary" />
<pi-avatar name="Grace Hopper" color="accent" size="large" />
<pi-avatar color="highlighted" shape="square" />
<pi-badge [value]="3" color="warn">
<pi-icon-button icon="notification-on" />
</pi-badge>
<pi-badge color="success">
<pi-avatar name="Ada Lovelace" color="primary" />
</pi-badge>
<pi-badge [value]="128" [max]="99" color="primary" position="top-left">
<pi-icon-button icon="email" />
</pi-badge>
</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."
@@ -481,6 +501,49 @@
</button>
</app-section>
<app-section
title="Drawer"
hint="pi-drawer tampoco se usa como etiqueta: se abre vía MatDialog con getDrawerConfigs, igual que pi-modal pero anclado a un borde."
>
<button pi-primary (click)="openDemoDrawer('right')">
Abrir drawer (derecha)
</button>
<button pi-outline (click)="openDemoDrawer('left')">
Abrir drawer (izquierda)
</button>
<ng-template #drawerTemplate>
<p>Contenido de ejemplo dentro del drawer.</p>
<p>Puede llevar cualquier plantilla: formularios, listas, filtros…</p>
</ng-template>
</app-section>
<app-section
title="Popover"
hint="pi-popover es una directiva ([pi-popover]) sobre @angular/cdk/overlay: no hay componente Material de contenido enriquecido que envolver."
>
<button
pi-outline
pi-popover
[popoverContent]="popoverTemplate"
popoverPosition="below"
>
Click (popover)
</button>
<button
pi-outline
pi-popover
[popoverContent]="popoverTemplate"
popoverPosition="above"
popoverTrigger="hover"
>
Hover (popover)
</button>
<ng-template #popoverTemplate let-close="close">
<p>Contenido del popover, con cierre programático.</p>
<button pi-ghost (click)="close()">Cerrar</button>
</ng-template>
</app-section>
<app-section title="Data grid">
<div class="stack full">
<pi-data-grid

View File

@@ -1,8 +1,10 @@
import {
ChangeDetectionStrategy,
Component,
TemplateRef,
inject,
signal,
viewChild,
} from '@angular/core';
import {
FormField,
@@ -19,6 +21,8 @@ import {
PiAccordionItemContentDirective,
PiAccordionItemDirective,
PiActionButtonComponent,
PiAvatarComponent,
PiBadgeComponent,
PiBannerComponent,
PiBaseDataTableComponent,
PiCardBottomContentDirective,
@@ -46,6 +50,9 @@ import {
DatasetColor,
Device,
PiDividerComponent,
DrawerConfigKeys,
PiDrawerComponent,
getDrawerConfigs,
PiEmailFieldComponent,
PiExpansionPanelComponent,
PiGhostButtonDirective,
@@ -83,6 +90,7 @@ import {
PiPanelExpandableContentDirective,
PiPasswordFieldComponent,
PiPhoneFieldComponent,
PiPopoverDirective,
PiProgressBarComponent,
PiRadioButtonComponent,
PiRange,
@@ -189,6 +197,8 @@ interface DemoModel {
PiIconComponent,
PiTextViewerComponent,
PiDividerComponent,
PiAvatarComponent,
PiBadgeComponent,
PiChipComponent,
PiChipStatusComponent,
PiChipValueComponent,
@@ -205,6 +215,7 @@ interface DemoModel {
PiListItemRightContentDirective,
PiListItemRightIconDirective,
PiTooltipDirective,
PiPopoverDirective,
PiNotFoundComponent,
// Feedback
PiProgressSpinnerComponent,
@@ -497,6 +508,23 @@ export class CatalogPage {
);
}
/* --- Drawer -------------------------------------------------------------- */
/** Igual que `pi-modal`: se abre desde TypeScript vía `MatDialog`. */
private readonly drawerTemplate =
viewChild.required<TemplateRef<unknown>>('drawerTemplate');
protected openDemoDrawer(side: 'left' | 'right'): void {
this.dialog.open(
PiDrawerComponent,
getDrawerConfigs(
side === 'left' ? DrawerConfigKeys.Left : DrawerConfigKeys.Right,
{ title: 'Detalle', template: this.drawerTemplate() },
),
);
this.log(`drawer abierto (${side})`);
}
/* --- Datos: grid, tabla, gráfico, carrusel ------------------------------ */
protected readonly employees = [