new components
This commit is contained in:
@@ -32,6 +32,7 @@ const TRANSLATIONS: TranslationObject = {
|
||||
reset: 'Borrar',
|
||||
search: 'Buscar…',
|
||||
noResults: 'Sin resultados',
|
||||
legend: 'Leyenda',
|
||||
validator: {
|
||||
requiredField: 'Este campo es obligatorio',
|
||||
onlyNumbers: 'Sólo se admiten números',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<pi-loader-bar />
|
||||
|
||||
<header class="page-header">
|
||||
<div class="page-header__top">
|
||||
<div>
|
||||
|
||||
@@ -14,5 +14,27 @@ export const routes: Routes = [
|
||||
loadComponent: () =>
|
||||
import('./pages/layouts/layouts.page').then((m) => m.LayoutsPage),
|
||||
},
|
||||
{
|
||||
path: 'ejemplos/login',
|
||||
title: 'Login · @pi/ui',
|
||||
loadComponent: () =>
|
||||
import('./pages/examples/login/login.page').then((m) => m.LoginPage),
|
||||
},
|
||||
{
|
||||
path: 'ejemplos/landing',
|
||||
title: 'Landing · @pi/ui',
|
||||
loadComponent: () =>
|
||||
import('./pages/examples/landing/landing.page').then(
|
||||
(m) => m.LandingPage,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'ejemplos/dashboard',
|
||||
title: 'Dashboard · @pi/ui',
|
||||
loadComponent: () =>
|
||||
import('./pages/examples/dashboard/dashboard.page').then(
|
||||
(m) => m.DashboardPage,
|
||||
),
|
||||
},
|
||||
{ path: '**', redirectTo: 'componentes' },
|
||||
];
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
|
||||
|
||||
import { PiSlideToggleComponent, ShellClass } from '@pi/ui';
|
||||
import { PiLoaderBarComponent, PiSlideToggleComponent, ShellClass } from '@pi/ui';
|
||||
|
||||
/**
|
||||
* Shell del playground: cabecera, navegación y estado de tema. El contenido de
|
||||
@@ -19,12 +19,21 @@ import { PiSlideToggleComponent, ShellClass } from '@pi/ui';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
imports: [RouterOutlet, RouterLink, RouterLinkActive, PiSlideToggleComponent],
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
RouterLink,
|
||||
RouterLinkActive,
|
||||
PiSlideToggleComponent,
|
||||
PiLoaderBarComponent,
|
||||
],
|
||||
})
|
||||
export class App {
|
||||
protected readonly navLinks = [
|
||||
{ path: '/componentes', label: 'Componentes' },
|
||||
{ path: '/layouts', label: 'Layouts' },
|
||||
{ path: '/ejemplos/login', label: 'Ejemplo: Login' },
|
||||
{ path: '/ejemplos/landing', label: 'Ejemplo: Landing' },
|
||||
{ path: '/ejemplos/dashboard', label: 'Ejemplo: Dashboard' },
|
||||
];
|
||||
|
||||
/* --- Tema y modo oscuro -------------------------------------------------- */
|
||||
|
||||
@@ -58,6 +58,14 @@
|
||||
<pi-slide-toggle [formField]="f.newsletter" />
|
||||
</label>
|
||||
|
||||
<label class="rating-field">
|
||||
<span>Satisfacción</span>
|
||||
<pi-rating [formField]="f.satisfaction" />
|
||||
</label>
|
||||
@if (f.satisfaction().errors().length > 0) {
|
||||
<span class="field-error">{{ f.satisfaction().errors()[0].message }}</span>
|
||||
}
|
||||
|
||||
<pi-slider
|
||||
[formField]="f.volume"
|
||||
unit="%"
|
||||
@@ -215,6 +223,82 @@
|
||||
</pi-badge>
|
||||
</app-section>
|
||||
|
||||
<app-section
|
||||
title="Avatar group"
|
||||
hint="pi-avatar-group pinta los pi-avatar internamente (a partir de items), por eso puede solaparlos con box-shadow como anillo sin tocar pi-avatar."
|
||||
>
|
||||
<pi-avatar-group [items]="avatarGroupItems" [max]="3" />
|
||||
<pi-avatar-group [items]="avatarGroupItems" size="small" />
|
||||
</app-section>
|
||||
|
||||
<app-section
|
||||
title="Breadcrumb, empty state y segmented control"
|
||||
hint="pi-breadcrumb usa routerLink en los elementos intermedios; pi-empty-state proyecta la acción; pi-segmented-control es genérico ([(value)])."
|
||||
>
|
||||
<div class="stack full">
|
||||
<pi-breadcrumb [items]="breadcrumbItems" />
|
||||
|
||||
<pi-segmented-control
|
||||
[options]="densityOptions"
|
||||
[(value)]="density"
|
||||
ariaLabel="Densidad"
|
||||
/>
|
||||
|
||||
<pi-empty-state
|
||||
icon="search"
|
||||
title="Sin resultados"
|
||||
description="No hay elementos que coincidan con el filtro aplicado."
|
||||
>
|
||||
<button pi-outline (click)="log('empty-state: limpiar filtros')">
|
||||
Limpiar filtros
|
||||
</button>
|
||||
</pi-empty-state>
|
||||
</div>
|
||||
</app-section>
|
||||
|
||||
<app-section
|
||||
title="Rating y Timeline"
|
||||
hint="pi-rating es interactivo ([(value)], con previsualización en hover); readonly lo deja solo en lectura. starTemplate sustituye el icono por defecto (aquí, corazones sin pi-icon). pi-timeline es de solo lectura, a partir de items."
|
||||
>
|
||||
<div class="stack full">
|
||||
<pi-rating [(value)]="rating" />
|
||||
<pi-rating [value]="4" [readonly]="true" color="primary" />
|
||||
|
||||
<pi-rating [(value)]="rating" [starTemplate]="heart" />
|
||||
<ng-template #heart let-filled="filled">
|
||||
<span class="heart-star" [class.is-filled]="filled">♥</span>
|
||||
</ng-template>
|
||||
|
||||
<pi-timeline [items]="timelineItems" />
|
||||
</div>
|
||||
</app-section>
|
||||
|
||||
<app-section
|
||||
title="Autocomplete y rango de fechas"
|
||||
hint="pi-autocomplete-field es un pi-text-field con sugerencias filtradas (menú propio, sin CDK Overlay); pi-date-range-field envuelve mat-date-range-input con dos fechas ([start, end]) en vez de una."
|
||||
>
|
||||
<div class="stack full">
|
||||
<pi-autocomplete-field
|
||||
[(value)]="autocompleteValue"
|
||||
[options]="cityOptions"
|
||||
placeholder="Escribe una ciudad…"
|
||||
>
|
||||
<pi-label>Ciudad</pi-label>
|
||||
</pi-autocomplete-field>
|
||||
|
||||
<pi-date-range-field [(value)]="dateRange">
|
||||
<pi-label>Periodo</pi-label>
|
||||
</pi-date-range-field>
|
||||
</div>
|
||||
</app-section>
|
||||
|
||||
<app-section
|
||||
title="Tree"
|
||||
hint="pi-tree es recursivo (pi-tree-node se auto-referencia, como ya hace pi-paginator): expande/colapsa por nodo y la selección burbujea hasta la raíz."
|
||||
>
|
||||
<pi-tree [nodes]="treeNodes" [(selectedId)]="treeSelectedId" />
|
||||
</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."
|
||||
@@ -293,6 +377,50 @@
|
||||
</pi-card>
|
||||
</app-section>
|
||||
|
||||
<app-section
|
||||
title="Stat card"
|
||||
hint="pi-stat-card extrae el patrón (icono + label + valor + tendencia) repetido a mano en login/landing/dashboard. No envuelve pi-card: aquí se anida dentro a propósito, igual que en el dashboard."
|
||||
>
|
||||
<div class="stat-card-grid">
|
||||
<pi-card
|
||||
alignment="flex-start"
|
||||
gap="s"
|
||||
innerPadding="m"
|
||||
context="private"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-center-content>
|
||||
<pi-stat-card
|
||||
icon="coins"
|
||||
label="Ingresos este mes"
|
||||
value="12.4K €"
|
||||
trend="+18%"
|
||||
[trendUp]="true"
|
||||
color="primary"
|
||||
/>
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
<pi-card
|
||||
alignment="flex-start"
|
||||
gap="s"
|
||||
innerPadding="m"
|
||||
context="private"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-center-content>
|
||||
<pi-stat-card
|
||||
icon="document-euro"
|
||||
label="Facturas pendientes"
|
||||
value="7"
|
||||
trend="-2"
|
||||
[trendUp]="false"
|
||||
color="accent"
|
||||
/>
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
</div>
|
||||
</app-section>
|
||||
|
||||
<app-section
|
||||
title="Panel"
|
||||
hint="pi-expansion-panel[type=form] usa Reactive Forms clásico, no Signal Forms."
|
||||
@@ -517,6 +645,20 @@
|
||||
</ng-template>
|
||||
</app-section>
|
||||
|
||||
<app-section
|
||||
title="Hub"
|
||||
hint="pi-hub es como pi-drawer pero anclado abajo: se abre vía MatDialog con getHubConfigs y entra deslizándose desde el borde inferior."
|
||||
>
|
||||
<button pi-primary (click)="openDemoHub('default')">Abrir hub</button>
|
||||
<button pi-outline (click)="openDemoHub('full')">
|
||||
Abrir hub (pantalla completa)
|
||||
</button>
|
||||
<ng-template #hubTemplate>
|
||||
<p>Contenido de ejemplo dentro del hub.</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."
|
||||
@@ -591,14 +733,61 @@
|
||||
</div>
|
||||
</app-section>
|
||||
|
||||
<app-section title="Chart">
|
||||
<div class="stack full">
|
||||
<pi-chart
|
||||
type="line"
|
||||
[device]="device"
|
||||
[xAxisMarks]="['Ene', 'Feb', 'Mar', 'Abr']"
|
||||
[datasets]="chartDatasets"
|
||||
/>
|
||||
<app-section
|
||||
title="Chart"
|
||||
hint="pi-line-chart, pi-bar-chart, pi-pie-chart, pi-doughnut-chart, pi-radar-chart y pi-gauge-chart son componentes independientes (cada uno registra solo lo suyo en Chart.js). line/bar/radar comparten datasets + pi-chart-legend; pie/doughnut usan slices y la leyenda nativa; gauge es un solo valor sobre un máximo."
|
||||
>
|
||||
<div class="chart-grid">
|
||||
<div class="chart-grid__item">
|
||||
<span class="chart-grid__label">pi-line-chart</span>
|
||||
<pi-line-chart
|
||||
[device]="device"
|
||||
[xAxisMarks]="['Ene', 'Feb', 'Mar', 'Abr']"
|
||||
[datasets]="chartDatasets"
|
||||
/>
|
||||
</div>
|
||||
<div class="chart-grid__item">
|
||||
<span class="chart-grid__label">pi-line-chart [filled]="true" (área)</span>
|
||||
<pi-line-chart
|
||||
[filled]="true"
|
||||
[device]="device"
|
||||
[xAxisMarks]="['Ene', 'Feb', 'Mar', 'Abr']"
|
||||
[datasets]="chartDatasets"
|
||||
/>
|
||||
</div>
|
||||
<div class="chart-grid__item">
|
||||
<span class="chart-grid__label">pi-bar-chart</span>
|
||||
<pi-bar-chart
|
||||
[device]="device"
|
||||
[xAxisMarks]="['Ene', 'Feb', 'Mar', 'Abr']"
|
||||
[datasets]="barChartDatasets"
|
||||
/>
|
||||
</div>
|
||||
<div class="chart-grid__item">
|
||||
<span class="chart-grid__label">pi-pie-chart</span>
|
||||
<pi-pie-chart [device]="device" [slices]="chartSlices" />
|
||||
</div>
|
||||
<div class="chart-grid__item">
|
||||
<span class="chart-grid__label">pi-doughnut-chart</span>
|
||||
<pi-doughnut-chart [device]="device" [slices]="chartSlices" />
|
||||
</div>
|
||||
<div class="chart-grid__item">
|
||||
<span class="chart-grid__label">pi-radar-chart</span>
|
||||
<pi-radar-chart
|
||||
[device]="device"
|
||||
[xAxisMarks]="['Velocidad', 'Fiabilidad', 'Precio', 'Soporte', 'Diseño']"
|
||||
[datasets]="radarDatasets"
|
||||
/>
|
||||
</div>
|
||||
<div class="chart-grid__item">
|
||||
<span class="chart-grid__label">pi-gauge-chart</span>
|
||||
<pi-gauge-chart
|
||||
[device]="device"
|
||||
[value]="72"
|
||||
[max]="100"
|
||||
label="satisfacción"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</app-section>
|
||||
|
||||
@@ -666,10 +855,13 @@
|
||||
|
||||
<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."
|
||||
hint="pi-spinner se omite a propósito: es un overlay position:fixed de página completa, no un indicador en línea. pi-loader-bar tampoco se usa como etiqueta con inputs: vive arriba del todo (ver app.html) y se muestra sola siguiendo LoaderFacade."
|
||||
>
|
||||
<pi-progress-spinner label="Progreso" [value]="65" [diameter]="72" />
|
||||
<pi-progress-bar label="Carga" [value]="65" />
|
||||
<button pi-outline (click)="triggerLoaderBar()">
|
||||
Simular carga (loader-bar arriba)
|
||||
</button>
|
||||
<pi-paginator
|
||||
[(currentPage)]="currentPage"
|
||||
[pageSize]="10"
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.heart-star {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
color: var(--pg-card-border);
|
||||
transition: color 0.1s ease-out;
|
||||
|
||||
&.is-filled {
|
||||
color: #e0245e;
|
||||
}
|
||||
}
|
||||
|
||||
.event {
|
||||
display: inline-block;
|
||||
margin-top: 0.75rem;
|
||||
@@ -44,6 +55,23 @@ main {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.field-error {
|
||||
margin-top: -0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--pi-color-warn);
|
||||
}
|
||||
|
||||
.rating-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
|
||||
> span {
|
||||
font-size: 0.75rem;
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
.state {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
@@ -91,3 +119,29 @@ pre {
|
||||
border: 1px dashed var(--pg-card-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.chart-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chart-grid__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.chart-grid__label {
|
||||
font-size: 0.78rem;
|
||||
font-family: monospace;
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
|
||||
.stat-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,15 @@ import {
|
||||
PiAccordionItemContentDirective,
|
||||
PiAccordionItemDirective,
|
||||
PiActionButtonComponent,
|
||||
AvatarGroupItem,
|
||||
PiAvatarComponent,
|
||||
PiAvatarGroupComponent,
|
||||
PiBadgeComponent,
|
||||
BreadcrumbItem,
|
||||
PiBreadcrumbComponent,
|
||||
PiEmptyStateComponent,
|
||||
SegmentedControlOption,
|
||||
PiSegmentedControlComponent,
|
||||
PiBannerComponent,
|
||||
PiBaseDataTableComponent,
|
||||
PiCardBottomContentDirective,
|
||||
@@ -33,7 +40,10 @@ import {
|
||||
PiCarouselItemDirective,
|
||||
PiCellDirective,
|
||||
PiCellTypeComponent,
|
||||
PiChartComponent,
|
||||
PiBarChartComponent,
|
||||
PiDoughnutChartComponent,
|
||||
PiLineChartComponent,
|
||||
PiPieChartComponent,
|
||||
PiChevronButtonComponent,
|
||||
PiChipActionComponent,
|
||||
PiChipComponent,
|
||||
@@ -46,6 +56,7 @@ import {
|
||||
PiCurrencyFieldComponent,
|
||||
PiDataGridComponent,
|
||||
PiDataTableItemComponent,
|
||||
ChartSlice,
|
||||
Dataset,
|
||||
DatasetColor,
|
||||
Device,
|
||||
@@ -54,6 +65,9 @@ import {
|
||||
PiDrawerComponent,
|
||||
getDrawerConfigs,
|
||||
PiEmailFieldComponent,
|
||||
HubConfigKeys,
|
||||
PiHubComponent,
|
||||
getHubConfigs,
|
||||
PiExpansionPanelComponent,
|
||||
PiGhostButtonDirective,
|
||||
PiHamburgerComponent,
|
||||
@@ -72,6 +86,7 @@ import {
|
||||
PiListItemLeftIconDirective,
|
||||
PiListItemRightContentDirective,
|
||||
PiListItemRightIconDirective,
|
||||
LoaderFacade,
|
||||
ModalConfigKeys,
|
||||
ModalStyleType,
|
||||
ModalType,
|
||||
@@ -95,6 +110,7 @@ import {
|
||||
PiRadioButtonComponent,
|
||||
PiRange,
|
||||
PiRangeSliderComponent,
|
||||
PiRatingComponent,
|
||||
PiSearchFieldComponent,
|
||||
PiSelectCarouselComponent,
|
||||
PiSelectComponent,
|
||||
@@ -128,6 +144,16 @@ import {
|
||||
PiDynamicMenuComponent,
|
||||
PiSkeletonComponent,
|
||||
PiStepperComponent,
|
||||
PiTimelineComponent,
|
||||
TimelineItem,
|
||||
PiStatCardComponent,
|
||||
PiRadarChartComponent,
|
||||
PiGaugeChartComponent,
|
||||
PiAutocompleteFieldComponent,
|
||||
PiDateRangeFieldComponent,
|
||||
DateRange,
|
||||
PiTreeComponent,
|
||||
TreeNode,
|
||||
} from '@pi/ui';
|
||||
|
||||
import { ShowcaseSection } from '../showcase-section';
|
||||
@@ -149,6 +175,7 @@ interface DemoModel {
|
||||
range: PiRange;
|
||||
volume: number;
|
||||
newsletter: boolean;
|
||||
satisfaction: number;
|
||||
search: string;
|
||||
website: string;
|
||||
phone: string;
|
||||
@@ -198,7 +225,19 @@ interface DemoModel {
|
||||
PiTextViewerComponent,
|
||||
PiDividerComponent,
|
||||
PiAvatarComponent,
|
||||
PiAvatarGroupComponent,
|
||||
PiBadgeComponent,
|
||||
PiBreadcrumbComponent,
|
||||
PiEmptyStateComponent,
|
||||
PiSegmentedControlComponent,
|
||||
PiRatingComponent,
|
||||
PiTimelineComponent,
|
||||
PiStatCardComponent,
|
||||
PiRadarChartComponent,
|
||||
PiGaugeChartComponent,
|
||||
PiAutocompleteFieldComponent,
|
||||
PiDateRangeFieldComponent,
|
||||
PiTreeComponent,
|
||||
PiChipComponent,
|
||||
PiChipStatusComponent,
|
||||
PiChipValueComponent,
|
||||
@@ -270,7 +309,10 @@ interface DemoModel {
|
||||
PiBaseDataTableComponent,
|
||||
PiDataTableItemComponent,
|
||||
PiCellTypeComponent,
|
||||
PiChartComponent,
|
||||
PiBarChartComponent,
|
||||
PiDoughnutChartComponent,
|
||||
PiLineChartComponent,
|
||||
PiPieChartComponent,
|
||||
PiCarouselComponent,
|
||||
PiCarouselItemDirective,
|
||||
// Componentes nuevos
|
||||
@@ -297,6 +339,7 @@ export class CatalogPage {
|
||||
range: { from: 0, to: 10 },
|
||||
volume: 40,
|
||||
newsletter: false,
|
||||
satisfaction: 0,
|
||||
search: '',
|
||||
website: '',
|
||||
phone: '',
|
||||
@@ -322,6 +365,7 @@ export class CatalogPage {
|
||||
maxLength(m.bio, 140, { message: 'Máximo 140 caracteres' });
|
||||
email(m.email, { message: 'Introduce un email válido' });
|
||||
min(m.age, 18, { message: 'Debes ser mayor de edad' });
|
||||
min(m.satisfaction, 1, { message: 'Valora con al menos una estrella' });
|
||||
});
|
||||
|
||||
/** Se muestra en el panel lateral para ver el modelo en vivo. */
|
||||
@@ -508,6 +552,14 @@ export class CatalogPage {
|
||||
);
|
||||
}
|
||||
|
||||
/** `pi-loader-bar` no tiene inputs: lee este estado compartido solo. */
|
||||
private readonly loader = inject(LoaderFacade);
|
||||
|
||||
protected triggerLoaderBar(): void {
|
||||
this.loader.showLoader();
|
||||
setTimeout(() => this.loader.hideLoader(), 2000);
|
||||
}
|
||||
|
||||
/* --- Drawer -------------------------------------------------------------- */
|
||||
|
||||
/** Igual que `pi-modal`: se abre desde TypeScript vía `MatDialog`. */
|
||||
@@ -525,6 +577,118 @@ export class CatalogPage {
|
||||
this.log(`drawer abierto (${side})`);
|
||||
}
|
||||
|
||||
/* --- Hub ------------------------------------------------------------------ */
|
||||
|
||||
/** Igual que `pi-drawer`, pero anclado abajo con un deslizamiento vertical. */
|
||||
private readonly hubTemplate =
|
||||
viewChild.required<TemplateRef<unknown>>('hubTemplate');
|
||||
|
||||
protected openDemoHub(size: 'default' | 'full'): void {
|
||||
this.dialog.open(
|
||||
PiHubComponent,
|
||||
getHubConfigs(size === 'full' ? HubConfigKeys.Full : HubConfigKeys.Default, {
|
||||
title: 'Detalle',
|
||||
template: this.hubTemplate(),
|
||||
}),
|
||||
);
|
||||
this.log(`hub abierto (${size})`);
|
||||
}
|
||||
|
||||
/* --- Avatar group, breadcrumb, segmented control ------------------------ */
|
||||
|
||||
protected readonly avatarGroupItems: AvatarGroupItem[] = [
|
||||
{ name: 'Ada Lovelace', color: 'primary' },
|
||||
{ name: 'Grace Hopper', color: 'accent' },
|
||||
{ image: 'https://i.pravatar.cc/128?img=12' },
|
||||
{ name: 'Margaret Hamilton', color: 'highlighted' },
|
||||
{ name: 'Katherine Johnson', color: 'grey' },
|
||||
];
|
||||
|
||||
protected readonly breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ label: 'Inicio', link: ['/'] },
|
||||
{ label: 'Componentes', link: ['/componentes'] },
|
||||
{ label: 'Avatar group' },
|
||||
];
|
||||
|
||||
protected readonly densityOptions: SegmentedControlOption<string>[] = [
|
||||
{ value: 'compact', label: 'Compacta' },
|
||||
{ value: 'comfortable', label: 'Cómoda' },
|
||||
];
|
||||
|
||||
protected readonly density = signal('comfortable');
|
||||
|
||||
protected readonly rating = signal(3);
|
||||
|
||||
protected readonly autocompleteValue = signal('');
|
||||
|
||||
protected readonly cityOptions: string[] = [
|
||||
'Barcelona',
|
||||
'Bilbao',
|
||||
'Madrid',
|
||||
'Málaga',
|
||||
'Sevilla',
|
||||
'Valencia',
|
||||
'Zaragoza',
|
||||
];
|
||||
|
||||
protected readonly dateRange = signal<DateRange>({ start: null, end: null });
|
||||
|
||||
protected readonly treeSelectedId = signal<string | undefined>(undefined);
|
||||
|
||||
protected readonly treeNodes: TreeNode[] = [
|
||||
{
|
||||
id: 'docs',
|
||||
label: 'Documentación',
|
||||
icon: 'document-euro',
|
||||
children: [
|
||||
{ id: 'docs-getting-started', label: 'Primeros pasos' },
|
||||
{
|
||||
id: 'docs-components',
|
||||
label: 'Componentes',
|
||||
children: [
|
||||
{ id: 'docs-components-form', label: 'Formularios' },
|
||||
{ id: 'docs-components-chart', label: 'Charts' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
label: 'Ajustes',
|
||||
icon: 'user',
|
||||
children: [
|
||||
{ id: 'settings-profile', label: 'Perfil' },
|
||||
{ id: 'settings-billing', label: 'Facturación' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly timelineItems: TimelineItem[] = [
|
||||
{
|
||||
title: 'Pedido creado',
|
||||
description: 'Se registró el pedido #4521.',
|
||||
date: '28 jul, 09:12',
|
||||
color: 'grey',
|
||||
},
|
||||
{
|
||||
title: 'Pago confirmado',
|
||||
description: 'Cargo aceptado por la pasarela de pago.',
|
||||
date: '28 jul, 09:14',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
title: 'Enviado',
|
||||
description: 'Paquete entregado al transportista.',
|
||||
date: '29 jul, 16:40',
|
||||
color: 'accent',
|
||||
},
|
||||
{
|
||||
title: 'Entregado',
|
||||
date: '31 jul, 11:05',
|
||||
color: 'success',
|
||||
},
|
||||
];
|
||||
|
||||
/* --- Datos: grid, tabla, gráfico, carrusel ------------------------------ */
|
||||
|
||||
protected readonly employees = [
|
||||
@@ -552,6 +716,42 @@ export class CatalogPage {
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly barChartDatasets: Dataset[] = [
|
||||
{
|
||||
label: 'Ventas',
|
||||
data: [65, 82, 74, 96],
|
||||
borderColor: DatasetColor.primary,
|
||||
backgroundColor: DatasetColor.primary,
|
||||
unit: ' uds.',
|
||||
unitPosition: 'after',
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly chartSlices: ChartSlice[] = [
|
||||
{ label: 'Básico', value: 38, color: DatasetColor.primary },
|
||||
{ label: 'Pro', value: 45, color: DatasetColor.accent },
|
||||
{ label: 'Empresas', value: 17, color: DatasetColor.highlighted },
|
||||
];
|
||||
|
||||
protected readonly radarDatasets: Dataset[] = [
|
||||
{
|
||||
label: 'Nuestro producto',
|
||||
data: [8, 7, 6, 9, 8],
|
||||
borderColor: DatasetColor.primary,
|
||||
backgroundColor: DatasetColor.primary,
|
||||
unit: '/10',
|
||||
unitPosition: 'after',
|
||||
},
|
||||
{
|
||||
label: 'Competencia',
|
||||
data: [6, 6, 8, 5, 6],
|
||||
borderColor: DatasetColor.grey,
|
||||
backgroundColor: DatasetColor.grey,
|
||||
unit: '/10',
|
||||
unitPosition: 'after',
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly carouselItems = [
|
||||
{ id: 1, label: 'Elemento 1' },
|
||||
{ id: 2, label: 'Elemento 2' },
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
<div class="dashboard">
|
||||
<aside class="dashboard__sidebar">
|
||||
<div class="dashboard__brand">
|
||||
<pi-icon icon="dashboard" color="primary" size="large" />
|
||||
<span>Nimbus</span>
|
||||
</div>
|
||||
|
||||
<pi-dynamic-menu
|
||||
class="dashboard__menu"
|
||||
[menuItems]="menuEntries()"
|
||||
(selectEntry)="onSelectMenuEntry($any($event))"
|
||||
/>
|
||||
|
||||
<div class="dashboard__sidebar-footer">
|
||||
<pi-avatar name="Marta Ruiz" color="primary" size="small" />
|
||||
<div>
|
||||
<strong>Marta Ruiz</strong>
|
||||
<span>COO, Nimbus</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="dashboard__main">
|
||||
<header class="dashboard__topbar">
|
||||
<pi-breadcrumb [items]="breadcrumbItems" />
|
||||
|
||||
<div class="dashboard__topbar-actions">
|
||||
<pi-search-field
|
||||
[formField]="f.query"
|
||||
placeholder="Buscar clientes o facturas…"
|
||||
/>
|
||||
<pi-badge [value]="3" color="warn">
|
||||
<pi-icon-button
|
||||
icon="notification-on"
|
||||
aria-label="Notificaciones"
|
||||
[hasPadding]="false"
|
||||
/>
|
||||
</pi-badge>
|
||||
<pi-avatar name="Marta Ruiz" color="primary" size="small" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="dashboard__stats">
|
||||
@for (stat of stats; track stat.label) {
|
||||
<pi-card
|
||||
alignment="flex-start"
|
||||
gap="s"
|
||||
innerPadding="l"
|
||||
context="private"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<div class="dashboard__stat-icon">
|
||||
<pi-icon [icon]="stat.icon" color="primary" size="medium" />
|
||||
</div>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<span class="dashboard__stat-label">{{ stat.label }}</span>
|
||||
<strong class="dashboard__stat-value">{{ stat.value }}</strong>
|
||||
</pi-card-center-content>
|
||||
<pi-card-bottom-content>
|
||||
<span
|
||||
class="dashboard__stat-trend"
|
||||
[class.is-down]="!stat.trendUp"
|
||||
>
|
||||
<pi-icon
|
||||
[icon]="stat.trendUp ? 'metric-up-arrow' : 'metric-down-arrow'"
|
||||
[color]="stat.trendUp ? 'success' : 'warn'"
|
||||
size="x-small"
|
||||
/>
|
||||
{{ stat.trend }}
|
||||
</span>
|
||||
</pi-card-bottom-content>
|
||||
</pi-card>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="dashboard__content">
|
||||
<div class="dashboard__content-main">
|
||||
<pi-card
|
||||
alignment="stretch"
|
||||
gap="m"
|
||||
innerPadding="l"
|
||||
context="private"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<div class="dashboard__card-header">
|
||||
<h3>Ingresos vs. gastos</h3>
|
||||
<button pi-ghost small>Ver informe</button>
|
||||
</div>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<pi-line-chart
|
||||
[device]="device"
|
||||
[xAxisMarks]="['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun']"
|
||||
[datasets]="chartDatasets"
|
||||
[isComparativeChart]="true"
|
||||
/>
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
|
||||
<pi-card
|
||||
alignment="stretch"
|
||||
gap="m"
|
||||
innerPadding="l"
|
||||
context="private"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<div class="dashboard__card-header">
|
||||
<h3>Últimas facturas</h3>
|
||||
<button pi-ghost small>Ver todas</button>
|
||||
</div>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<div class="dashboard__table">
|
||||
<div class="dashboard__table-row dashboard__table-row--head">
|
||||
<span>Cliente</span>
|
||||
<span>Importe</span>
|
||||
<span>Fecha</span>
|
||||
<span>Estado</span>
|
||||
</div>
|
||||
@for (row of invoices; track row.client) {
|
||||
<div class="dashboard__table-row">
|
||||
<span>{{ row.client }}</span>
|
||||
<span>{{ row.amount }}</span>
|
||||
<span>{{ row.date }}</span>
|
||||
<pi-chip-status [status]="row.status" [label]="row.statusLabel" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
|
||||
<pi-card
|
||||
alignment="stretch"
|
||||
gap="m"
|
||||
innerPadding="l"
|
||||
context="private"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<div class="dashboard__card-header">
|
||||
<h3>Nuevos clientes por mes</h3>
|
||||
<button pi-ghost small>Ver informe</button>
|
||||
</div>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<pi-bar-chart
|
||||
[device]="device"
|
||||
[xAxisMarks]="['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun']"
|
||||
[datasets]="newClientsDatasets"
|
||||
[isComparativeChart]="false"
|
||||
/>
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
</div>
|
||||
|
||||
<div class="dashboard__content-side">
|
||||
<pi-card
|
||||
alignment="stretch"
|
||||
gap="m"
|
||||
innerPadding="l"
|
||||
context="private"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<h3>Facturas por estado</h3>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<pi-doughnut-chart [device]="device" [slices]="invoiceStatusSlices" />
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
|
||||
<pi-card
|
||||
alignment="stretch"
|
||||
gap="m"
|
||||
innerPadding="l"
|
||||
context="private"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<h3>Actividad reciente</h3>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<pi-timeline [items]="timelineItems" />
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,222 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
display: grid;
|
||||
grid-template-columns: 16rem minmax(0, 1fr);
|
||||
gap: var(--pi-spacing-x-large);
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.dashboard {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Sidebar ---------- */
|
||||
|
||||
.dashboard__sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-x-large);
|
||||
padding: var(--pi-spacing-large);
|
||||
border-radius: var(--pi-border-radius-large);
|
||||
background: var(--pg-card-bg);
|
||||
border: 1px solid var(--pg-card-border);
|
||||
}
|
||||
|
||||
.dashboard__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-xx-small);
|
||||
font-weight: 700;
|
||||
font-size: var(--pi-font-size-medium);
|
||||
color: var(--pg-page-text);
|
||||
padding: 0 var(--pi-spacing-xx-small);
|
||||
}
|
||||
|
||||
.dashboard__menu {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dashboard__sidebar-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-small);
|
||||
padding: var(--pi-spacing-small) var(--pi-spacing-xx-small) 0;
|
||||
border-top: 1px solid var(--pg-card-border);
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
font-size: var(--pi-font-size-xxx-small);
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Main column ---------- */
|
||||
|
||||
.dashboard__main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-x-large);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard__topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--pi-spacing-large);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dashboard__topbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-medium);
|
||||
}
|
||||
|
||||
/* ---------- Stats ---------- */
|
||||
|
||||
.dashboard__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: var(--pi-spacing-large);
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard__stats {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.dashboard__stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard__stat-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: var(--pi-border-radius-medium);
|
||||
background: var(--pi-color-primary-25);
|
||||
}
|
||||
|
||||
.dashboard__stat-label {
|
||||
display: block;
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
|
||||
.dashboard__stat-value {
|
||||
display: block;
|
||||
margin-top: var(--pi-spacing-xxxx-small);
|
||||
font-size: var(--pi-font-size-xx-large);
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
|
||||
.dashboard__stat-trend {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-xxxx-small);
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
font-weight: 600;
|
||||
color: var(--pi-color-success);
|
||||
|
||||
&.is-down {
|
||||
color: var(--pi-color-warn);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Contenido: gráfico + tabla / actividad ---------- */
|
||||
|
||||
.dashboard__content {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
|
||||
gap: var(--pi-spacing-large);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.dashboard__content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard__content-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-large);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard__content-side {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-large);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard__card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--pi-spacing-small);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: var(--pi-font-size-medium);
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard__content-side pi-card h3 {
|
||||
margin: 0;
|
||||
font-size: var(--pi-font-size-medium);
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
|
||||
/* ---------- Tabla de facturas ---------- */
|
||||
|
||||
.dashboard__table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dashboard__table-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr) minmax(
|
||||
0,
|
||||
0.8fr
|
||||
);
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-medium);
|
||||
padding: var(--pi-spacing-small) var(--pi-spacing-xx-small);
|
||||
border-bottom: 1px solid var(--pg-card-border);
|
||||
font-size: var(--pi-font-size-small);
|
||||
color: var(--pg-page-text);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard__table-row--head {
|
||||
font-size: var(--pi-font-size-xxx-small);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--pg-text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
||||
import { FormField, form } from '@angular/forms/signals';
|
||||
|
||||
import {
|
||||
PiAvatarComponent,
|
||||
PiBadgeComponent,
|
||||
PiBarChartComponent,
|
||||
BreadcrumbItem,
|
||||
PiBreadcrumbComponent,
|
||||
PiCardBottomContentDirective,
|
||||
PiCardCenterContentDirective,
|
||||
PiCardComponent,
|
||||
PiCardTopContentDirective,
|
||||
ChartSlice,
|
||||
PiDoughnutChartComponent,
|
||||
PiLineChartComponent,
|
||||
PiChipStatusComponent,
|
||||
Dataset,
|
||||
DatasetColor,
|
||||
Device,
|
||||
PiDynamicMenuComponent,
|
||||
PiGhostButtonDirective,
|
||||
PiIconButtonComponent,
|
||||
PiIconComponent,
|
||||
PiSearchFieldComponent,
|
||||
PiSmallButtonDirective,
|
||||
PiTimelineComponent,
|
||||
TimelineItem,
|
||||
MenuEntry,
|
||||
} from '@pi/ui';
|
||||
|
||||
interface SearchModel {
|
||||
query: string;
|
||||
}
|
||||
|
||||
interface Stat {
|
||||
icon: string;
|
||||
label: string;
|
||||
value: string;
|
||||
trend: string;
|
||||
trendUp: boolean;
|
||||
}
|
||||
|
||||
interface InvoiceRow {
|
||||
client: string;
|
||||
amount: string;
|
||||
date: string;
|
||||
status: 'success' | 'pending' | 'error';
|
||||
statusLabel: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './dashboard.page.html',
|
||||
styleUrl: './dashboard.page.scss',
|
||||
imports: [
|
||||
FormField,
|
||||
PiAvatarComponent,
|
||||
PiBadgeComponent,
|
||||
PiBreadcrumbComponent,
|
||||
PiCardBottomContentDirective,
|
||||
PiCardCenterContentDirective,
|
||||
PiCardComponent,
|
||||
PiCardTopContentDirective,
|
||||
PiBarChartComponent,
|
||||
PiDoughnutChartComponent,
|
||||
PiLineChartComponent,
|
||||
PiChipStatusComponent,
|
||||
PiDynamicMenuComponent,
|
||||
PiGhostButtonDirective,
|
||||
PiIconButtonComponent,
|
||||
PiIconComponent,
|
||||
PiSearchFieldComponent,
|
||||
PiSmallButtonDirective,
|
||||
PiTimelineComponent,
|
||||
],
|
||||
})
|
||||
export class DashboardPage {
|
||||
protected readonly device: Device = 'DESKTOP';
|
||||
|
||||
protected readonly menuEntries = signal<MenuEntry[]>([
|
||||
{ id: 1, label: 'Panel', icon: 'dashboard', isActive: true },
|
||||
{ id: 2, label: 'Clientes', icon: 'briefcase' },
|
||||
{ id: 3, label: 'Facturas', icon: 'document-euro' },
|
||||
{ id: 4, label: 'Pagos', icon: 'coins' },
|
||||
{ id: 5, label: 'Ajustes', icon: 'user' },
|
||||
]);
|
||||
|
||||
protected readonly breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ label: 'Nimbus', link: ['/ejemplos/dashboard'] },
|
||||
{ label: 'Panel' },
|
||||
];
|
||||
|
||||
private readonly searchModel = signal<SearchModel>({ query: '' });
|
||||
|
||||
protected readonly f = form(this.searchModel, () => {});
|
||||
|
||||
protected readonly stats: Stat[] = [
|
||||
{
|
||||
icon: 'coins',
|
||||
label: 'Ingresos este mes',
|
||||
value: '12.4K €',
|
||||
trend: '+18%',
|
||||
trendUp: true,
|
||||
},
|
||||
{
|
||||
icon: 'document-euro',
|
||||
label: 'Facturas pendientes',
|
||||
value: '7',
|
||||
trend: '-2',
|
||||
trendUp: false,
|
||||
},
|
||||
{
|
||||
icon: 'briefcase',
|
||||
label: 'Clientes activos',
|
||||
value: '184',
|
||||
trend: '+5',
|
||||
trendUp: true,
|
||||
},
|
||||
{
|
||||
icon: 'time-detail',
|
||||
label: 'Tiempo medio de cobro',
|
||||
value: '9 días',
|
||||
trend: '-1,2',
|
||||
trendUp: true,
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly chartDatasets: Dataset[] = [
|
||||
{
|
||||
label: 'Ingresos',
|
||||
data: [38, 52, 47, 63, 59, 74],
|
||||
borderColor: DatasetColor.primary,
|
||||
backgroundColor: DatasetColor.primary,
|
||||
cubicInterpolationMode: 'monotone',
|
||||
unit: 'K €',
|
||||
unitPosition: 'after',
|
||||
},
|
||||
{
|
||||
label: 'Gastos',
|
||||
data: [22, 26, 24, 30, 28, 31],
|
||||
borderColor: DatasetColor.grey,
|
||||
backgroundColor: DatasetColor.grey,
|
||||
cubicInterpolationMode: 'monotone',
|
||||
unit: 'K €',
|
||||
unitPosition: 'after',
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly newClientsDatasets: Dataset[] = [
|
||||
{
|
||||
label: 'Nuevos clientes',
|
||||
data: [8, 12, 9, 14, 11, 17],
|
||||
borderColor: DatasetColor.highlighted,
|
||||
backgroundColor: DatasetColor.highlighted,
|
||||
unit: '',
|
||||
unitPosition: 'after',
|
||||
},
|
||||
];
|
||||
|
||||
/** Coincide con el desglose real de `invoices`: 2 pagadas, 2 pendientes, 1 rechazada. */
|
||||
protected readonly invoiceStatusSlices: ChartSlice[] = [
|
||||
{ label: 'Pagadas', value: 2, color: DatasetColor.success },
|
||||
{ label: 'Pendientes', value: 2, color: DatasetColor.accent },
|
||||
{ label: 'Rechazadas', value: 1, color: DatasetColor.warn },
|
||||
];
|
||||
|
||||
protected readonly invoices: InvoiceRow[] = [
|
||||
{
|
||||
client: 'Cocina Litoral',
|
||||
amount: '1.250,00 €',
|
||||
date: '28/07/2026',
|
||||
status: 'success',
|
||||
statusLabel: 'Pagada',
|
||||
},
|
||||
{
|
||||
client: 'Bloom Studio',
|
||||
amount: '890,50 €',
|
||||
date: '26/07/2026',
|
||||
status: 'pending',
|
||||
statusLabel: 'Pendiente',
|
||||
},
|
||||
{
|
||||
client: 'Vela Textil',
|
||||
amount: '2.140,00 €',
|
||||
date: '22/07/2026',
|
||||
status: 'success',
|
||||
statusLabel: 'Pagada',
|
||||
},
|
||||
{
|
||||
client: 'Norte Legal',
|
||||
amount: '430,00 €',
|
||||
date: '18/07/2026',
|
||||
status: 'error',
|
||||
statusLabel: 'Rechazada',
|
||||
},
|
||||
{
|
||||
client: 'Rueda Logística',
|
||||
amount: '675,25 €',
|
||||
date: '15/07/2026',
|
||||
status: 'pending',
|
||||
statusLabel: 'Pendiente',
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly timelineItems: TimelineItem[] = [
|
||||
{
|
||||
title: 'Factura #4521 pagada',
|
||||
description: 'Cocina Litoral · 1.250,00 €',
|
||||
date: 'Hoy, 09:12',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
title: 'Nuevo cliente',
|
||||
description: 'Rueda Logística se ha dado de alta.',
|
||||
date: 'Ayer, 17:40',
|
||||
color: 'accent',
|
||||
},
|
||||
{
|
||||
title: 'Factura #4498 rechazada',
|
||||
description: 'Norte Legal · pago rechazado por el banco.',
|
||||
date: 'Ayer, 11:05',
|
||||
color: 'grey',
|
||||
},
|
||||
{
|
||||
title: 'Recordatorio enviado',
|
||||
description: 'Bloom Studio · factura #4510 pendiente.',
|
||||
date: '26 jul, 16:20',
|
||||
color: 'success',
|
||||
},
|
||||
];
|
||||
|
||||
protected onSelectMenuEntry(entry: MenuEntry): void {
|
||||
this.menuEntries.update((entries) =>
|
||||
entries.map((item) => ({ ...item, isActive: item.id === entry.id })),
|
||||
);
|
||||
}
|
||||
}
|
||||
242
apps/playground/src/app/pages/examples/landing/landing.page.html
Normal file
242
apps/playground/src/app/pages/examples/landing/landing.page.html
Normal file
@@ -0,0 +1,242 @@
|
||||
<div class="landing">
|
||||
<!-- Nav -->
|
||||
<nav class="landing__nav">
|
||||
<div class="landing__logo">
|
||||
<pi-icon icon="dashboard" color="primary" size="large" />
|
||||
<span>Nimbus</span>
|
||||
</div>
|
||||
<div class="landing__nav-links">
|
||||
<a href="javascript:void(0)">Producto</a>
|
||||
<a href="javascript:void(0)">Precios</a>
|
||||
<a href="javascript:void(0)">Clientes</a>
|
||||
</div>
|
||||
<div class="landing__nav-actions">
|
||||
<button pi-outline small>Iniciar sesión</button>
|
||||
<button pi-primary small>Empezar gratis</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero -->
|
||||
<header class="landing__hero">
|
||||
<div class="landing__hero-copy">
|
||||
<pi-chip label="Nuevo: previsión de caja con IA" color="primary" />
|
||||
<h1>Todo el control de tu negocio, en un solo panel.</h1>
|
||||
<p>
|
||||
Nimbus conecta tu facturación, tus clientes y tu banco para que dejes
|
||||
de perseguir números en hojas de cálculo y empieces a tomar
|
||||
decisiones con datos en tiempo real.
|
||||
</p>
|
||||
<div class="landing__hero-actions">
|
||||
<button pi-primary large>Empieza gratis 14 días</button>
|
||||
<button pi-outline large>Ver una demo</button>
|
||||
</div>
|
||||
<p class="landing__hero-note">
|
||||
Sin tarjeta de crédito · Cancela cuando quieras
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="landing__hero-preview">
|
||||
<pi-card
|
||||
alignment="stretch"
|
||||
gap="m"
|
||||
innerPadding="l"
|
||||
context="public"
|
||||
color="white"
|
||||
class="landing__preview-card"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<div class="landing__preview-header">
|
||||
<span>Ingresos</span>
|
||||
<pi-chip-status status="success" label="+18% vs. mes anterior" />
|
||||
</div>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<pi-line-chart
|
||||
[device]="device"
|
||||
[xAxisMarks]="['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun']"
|
||||
[datasets]="heroChartData"
|
||||
/>
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Prueba social -->
|
||||
<section class="landing__logos">
|
||||
<span>Con la confianza de equipos en</span>
|
||||
<div class="landing__logos-row">
|
||||
<span>Cocina Litoral</span>
|
||||
<span>Bloom Studio</span>
|
||||
<span>Vela Textil</span>
|
||||
<span>Norte Legal</span>
|
||||
<span>Rueda Logística</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="landing__section">
|
||||
<div class="landing__section-heading">
|
||||
<h2>Todo lo que necesitas para llevar las cuentas</h2>
|
||||
<p>Sin implementaciones eternas ni consultores. Listo en un día.</p>
|
||||
</div>
|
||||
|
||||
<div class="landing__features">
|
||||
@for (feature of features; track feature.title) {
|
||||
<pi-card
|
||||
alignment="flex-start"
|
||||
gap="s"
|
||||
innerPadding="l"
|
||||
context="public"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<div class="landing__feature-icon">
|
||||
<pi-icon [icon]="feature.icon" color="primary" size="medium" />
|
||||
</div>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<h3>{{ feature.title }}</h3>
|
||||
<p>{{ feature.description }}</p>
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Stats -->
|
||||
<section class="landing__stats">
|
||||
@for (stat of stats; track stat.label) {
|
||||
<div class="landing__stat">
|
||||
<strong>{{ stat.value }}</strong>
|
||||
<span>{{ stat.label }}</span>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<!-- Testimonios -->
|
||||
<section class="landing__section">
|
||||
<div class="landing__section-heading">
|
||||
<h2>Lo que dicen los equipos que ya usan Nimbus</h2>
|
||||
</div>
|
||||
|
||||
<div class="landing__testimonials">
|
||||
@for (testimonial of testimonials; track testimonial.name) {
|
||||
<pi-card
|
||||
alignment="flex-start"
|
||||
gap="m"
|
||||
innerPadding="l"
|
||||
context="public"
|
||||
color="white"
|
||||
>
|
||||
<pi-card-center-content>
|
||||
<p class="landing__testimonial-quote">«{{ testimonial.quote }}»</p>
|
||||
</pi-card-center-content>
|
||||
<pi-card-bottom-content>
|
||||
<div class="landing__testimonial-author">
|
||||
<pi-avatar [name]="testimonial.name" [color]="testimonial.color" />
|
||||
<div>
|
||||
<strong>{{ testimonial.name }}</strong>
|
||||
<span>{{ testimonial.role }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</pi-card-bottom-content>
|
||||
</pi-card>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Precios -->
|
||||
<section class="landing__section" id="precios">
|
||||
<div class="landing__section-heading">
|
||||
<h2>Un plan para cada etapa</h2>
|
||||
<p>Cambia de plan cuando quieras, sin penalizaciones.</p>
|
||||
</div>
|
||||
|
||||
<div class="landing__plans">
|
||||
@for (plan of plans; track plan.name) {
|
||||
<pi-card
|
||||
alignment="stretch"
|
||||
gap="m"
|
||||
innerPadding="xl"
|
||||
context="public"
|
||||
[color]="plan.isFeatured ? 'primary' : 'white'"
|
||||
[class.landing__plan--featured]="plan.isFeatured"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
@if (plan.isFeatured) {
|
||||
<pi-chip label="Más popular" color="primary-white" />
|
||||
}
|
||||
<h3>{{ plan.name }}</h3>
|
||||
<p class="landing__plan-description">{{ plan.description }}</p>
|
||||
<p class="landing__plan-price">
|
||||
<strong>{{ plan.price }} €</strong>
|
||||
<span>{{ plan.period }}</span>
|
||||
</p>
|
||||
</pi-card-top-content>
|
||||
<pi-card-center-content>
|
||||
<ul class="landing__plan-features">
|
||||
@for (item of plan.features; track item) {
|
||||
<li>
|
||||
<pi-icon
|
||||
icon="check-rounded"
|
||||
[color]="plan.isFeatured ? 'white' : 'primary'"
|
||||
size="small"
|
||||
/>
|
||||
<span>{{ item }}</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</pi-card-center-content>
|
||||
<pi-card-bottom-content>
|
||||
@if (plan.isFeatured) {
|
||||
<button pi-white w100>{{ plan.cta }}</button>
|
||||
} @else {
|
||||
<button pi-outline w100>{{ plan.cta }}</button>
|
||||
}
|
||||
</pi-card-bottom-content>
|
||||
</pi-card>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ -->
|
||||
<section class="landing__section landing__section--narrow">
|
||||
<div class="landing__section-heading">
|
||||
<h2>Preguntas frecuentes</h2>
|
||||
</div>
|
||||
|
||||
<pi-accordion
|
||||
color="primary"
|
||||
appearance="outline"
|
||||
[(expandedItemIndex)]="expandedFaqIndex"
|
||||
>
|
||||
@for (faq of faqs; track faq.question) {
|
||||
<pi-accordion-item
|
||||
[title]="faq.question"
|
||||
[status]="faq.tagStatus"
|
||||
[statusLabel]="faq.tag"
|
||||
>
|
||||
<ng-template pi-accordion-item-expandable-content>
|
||||
<p>{{ faq.answer }}</p>
|
||||
</ng-template>
|
||||
</pi-accordion-item>
|
||||
}
|
||||
</pi-accordion>
|
||||
</section>
|
||||
|
||||
<!-- CTA final -->
|
||||
<section class="landing__cta">
|
||||
<h2>Deja de perseguir números. Empieza a decidir con datos.</h2>
|
||||
<p>Prueba Nimbus gratis durante 14 días. Sin tarjeta de crédito.</p>
|
||||
<button pi-white large>Crear cuenta gratis</button>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="landing__footer">
|
||||
<div class="landing__logo">
|
||||
<pi-icon icon="dashboard" color="grey" size="medium" />
|
||||
<span>Nimbus</span>
|
||||
</div>
|
||||
<p>© 2026 Nimbus. Pantalla de ejemplo generada con @pi/ui.</p>
|
||||
</footer>
|
||||
</div>
|
||||
409
apps/playground/src/app/pages/examples/landing/landing.page.scss
Normal file
409
apps/playground/src/app/pages/examples/landing/landing.page.scss
Normal file
@@ -0,0 +1,409 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.landing {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-xxxx-large);
|
||||
max-width: 72rem;
|
||||
margin: 0 auto;
|
||||
padding-bottom: var(--pi-spacing-xxxx-large);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
|
||||
/* ---------- Nav ---------- */
|
||||
|
||||
.landing__nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--pi-spacing-large);
|
||||
padding: var(--pi-spacing-medium) 0;
|
||||
}
|
||||
|
||||
.landing__nav-links {
|
||||
display: flex;
|
||||
gap: var(--pi-spacing-x-large);
|
||||
|
||||
a {
|
||||
color: var(--pg-text-muted);
|
||||
text-decoration: none;
|
||||
font-size: var(--pi-font-size-small);
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
color: var(--pi-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.landing__nav-actions {
|
||||
display: flex;
|
||||
gap: var(--pi-spacing-small);
|
||||
}
|
||||
|
||||
.landing__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-xx-small);
|
||||
font-weight: 700;
|
||||
font-size: var(--pi-font-size-medium);
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
|
||||
/* ---------- Hero ---------- */
|
||||
|
||||
.landing__hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: var(--pi-spacing-xxx-large);
|
||||
align-items: center;
|
||||
padding: var(--pi-spacing-x-large) 0 var(--pi-spacing-xxxx-large);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.landing__hero {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.landing__hero-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--pi-spacing-large);
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: var(--pi-font-size-xxx-large);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
color: var(--pg-text-muted);
|
||||
font-size: var(--pi-font-size-medium);
|
||||
line-height: 1.6;
|
||||
max-width: 32rem;
|
||||
}
|
||||
}
|
||||
|
||||
.landing__hero-actions {
|
||||
display: flex;
|
||||
gap: var(--pi-spacing-small);
|
||||
}
|
||||
|
||||
.landing__hero-note {
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
|
||||
.landing__preview-card {
|
||||
box-shadow: 0 40px 80px -40px rgba(20, 30, 70, 0.35) !important;
|
||||
}
|
||||
|
||||
.landing__preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
font-weight: 700;
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Logos ---------- */
|
||||
|
||||
.landing__logos {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-medium);
|
||||
padding: var(--pi-spacing-large) 0;
|
||||
border-top: 1px solid var(--pg-card-border);
|
||||
border-bottom: 1px solid var(--pg-card-border);
|
||||
|
||||
> span {
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
.landing__logos-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: var(--pi-spacing-xx-large);
|
||||
|
||||
span {
|
||||
font-weight: 700;
|
||||
color: var(--pg-text-muted);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Secciones genéricas ---------- */
|
||||
|
||||
.landing__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-xx-large);
|
||||
}
|
||||
|
||||
.landing__section--narrow {
|
||||
max-width: 42rem;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.landing__section-heading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-xxx-small);
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: var(--pi-font-size-xx-large);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Features ---------- */
|
||||
|
||||
.landing__features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: var(--pi-spacing-large);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.landing__features {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.landing__features {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.landing__feature-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
border-radius: var(--pi-border-radius-medium);
|
||||
background: var(--pi-color-primary-25);
|
||||
}
|
||||
|
||||
.landing__features h3 {
|
||||
margin: 0 0 var(--pi-spacing-xxx-small);
|
||||
font-size: var(--pi-font-size-medium);
|
||||
}
|
||||
|
||||
.landing__features p {
|
||||
margin: 0;
|
||||
color: var(--pg-text-muted);
|
||||
font-size: var(--pi-font-size-small);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ---------- Stats ---------- */
|
||||
|
||||
.landing__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: var(--pi-spacing-large);
|
||||
padding: var(--pi-spacing-xx-large);
|
||||
border-radius: var(--pi-border-radius-large);
|
||||
background: var(--pg-card-bg);
|
||||
border: 1px solid var(--pg-card-border);
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.landing__stats {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.landing__stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-xxxx-small);
|
||||
text-align: center;
|
||||
|
||||
strong {
|
||||
font-size: var(--pi-font-size-xx-large);
|
||||
color: var(--pi-color-primary);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Testimonios ---------- */
|
||||
|
||||
.landing__testimonials {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--pi-spacing-large);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.landing__testimonials {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.landing__testimonial-quote {
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
color: var(--pg-page-text);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.landing__testimonial-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-small);
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
font-size: var(--pi-font-size-small);
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
font-size: var(--pi-font-size-xxx-small);
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Precios ---------- */
|
||||
|
||||
.landing__plans {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--pi-spacing-large);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.landing__plans {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.landing__plan--featured {
|
||||
transform: scale(1.03);
|
||||
border: 2px solid var(--pi-color-primary);
|
||||
box-shadow: 0 24px 48px -24px rgba(87, 114, 212, 0.45) !important;
|
||||
}
|
||||
|
||||
.landing__plans h3 {
|
||||
margin: var(--pi-spacing-small) 0 0;
|
||||
font-size: var(--pi-font-size-large);
|
||||
}
|
||||
|
||||
.landing__plan-description {
|
||||
margin: var(--pi-spacing-xxxx-small) 0 0;
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.landing__plan-price {
|
||||
margin: var(--pi-spacing-medium) 0 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--pi-spacing-xxxx-small);
|
||||
|
||||
strong {
|
||||
font-size: var(--pi-font-size-xxx-large);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
.landing__plan-features {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-small);
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-xx-small);
|
||||
font-size: var(--pi-font-size-small);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- CTA final ---------- */
|
||||
|
||||
.landing__cta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-medium);
|
||||
text-align: center;
|
||||
padding: var(--pi-spacing-xxxx-large);
|
||||
border-radius: var(--pi-border-radius-large);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--pi-color-primary-900),
|
||||
var(--pi-color-highlighted)
|
||||
);
|
||||
color: var(--pi-color-white);
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: var(--pi-color-white);
|
||||
font-size: var(--pi-font-size-xx-large);
|
||||
max-width: 34rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--pi-color-primary-100);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Footer ---------- */
|
||||
|
||||
.landing__footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-small);
|
||||
padding-top: var(--pi-spacing-x-large);
|
||||
border-top: 1px solid var(--pg-card-border);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: var(--pi-font-size-xxx-small);
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
}
|
||||
220
apps/playground/src/app/pages/examples/landing/landing.page.ts
Normal file
220
apps/playground/src/app/pages/examples/landing/landing.page.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
||||
|
||||
import {
|
||||
PiAccordionComponent,
|
||||
PiAccordionItemContentDirective,
|
||||
PiAccordionItemDirective,
|
||||
PiAvatarComponent,
|
||||
PiCardBottomContentDirective,
|
||||
PiCardCenterContentDirective,
|
||||
PiCardComponent,
|
||||
PiCardTopContentDirective,
|
||||
PiLineChartComponent,
|
||||
PiChipComponent,
|
||||
PiChipStatusComponent,
|
||||
PiColorPrimaryDirective,
|
||||
PiColorWhiteDirective,
|
||||
Dataset,
|
||||
DatasetColor,
|
||||
Device,
|
||||
PiIconComponent,
|
||||
PiLargeButtonDirective,
|
||||
PiOutlineButtonDirective,
|
||||
PiSmallButtonDirective,
|
||||
PiW100ButtonDirective,
|
||||
} from '@pi/ui';
|
||||
|
||||
interface Feature {
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Plan {
|
||||
name: string;
|
||||
price: string;
|
||||
period: string;
|
||||
description: string;
|
||||
isFeatured: boolean;
|
||||
features: string[];
|
||||
cta: string;
|
||||
}
|
||||
|
||||
interface Faq {
|
||||
question: string;
|
||||
answer: string;
|
||||
tagStatus: 'success' | 'info' | 'pending';
|
||||
tag: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './landing.page.html',
|
||||
styleUrl: './landing.page.scss',
|
||||
imports: [
|
||||
PiAccordionComponent,
|
||||
PiAccordionItemContentDirective,
|
||||
PiAccordionItemDirective,
|
||||
PiAvatarComponent,
|
||||
PiCardBottomContentDirective,
|
||||
PiCardCenterContentDirective,
|
||||
PiCardComponent,
|
||||
PiCardTopContentDirective,
|
||||
PiLineChartComponent,
|
||||
PiChipComponent,
|
||||
PiChipStatusComponent,
|
||||
PiColorPrimaryDirective,
|
||||
PiColorWhiteDirective,
|
||||
PiIconComponent,
|
||||
PiLargeButtonDirective,
|
||||
PiOutlineButtonDirective,
|
||||
PiSmallButtonDirective,
|
||||
PiW100ButtonDirective,
|
||||
],
|
||||
})
|
||||
export class LandingPage {
|
||||
protected readonly device: Device = 'DESKTOP';
|
||||
|
||||
protected readonly heroChartData: Dataset[] = [
|
||||
{
|
||||
label: 'Ingresos',
|
||||
data: [38, 52, 47, 63, 59, 74],
|
||||
borderColor: DatasetColor.primary,
|
||||
backgroundColor: DatasetColor.primary,
|
||||
cubicInterpolationMode: 'monotone',
|
||||
unit: 'K €',
|
||||
unitPosition: 'after',
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly features: Feature[] = [
|
||||
{
|
||||
icon: 'dashboard',
|
||||
title: 'Panel unificado',
|
||||
description:
|
||||
'Ventas, gastos y caja en una sola vista, actualizada al segundo.',
|
||||
},
|
||||
{
|
||||
icon: 'document-euro',
|
||||
title: 'Facturación automática',
|
||||
description:
|
||||
'Genera y envía facturas recurrentes sin tocar una hoja de cálculo.',
|
||||
},
|
||||
{
|
||||
icon: 'briefcase',
|
||||
title: 'Gestión de clientes',
|
||||
description: 'Historial de pedidos, notas y contratos en una ficha.',
|
||||
},
|
||||
{
|
||||
icon: 'connect',
|
||||
title: 'Se integra con tu stack',
|
||||
description:
|
||||
'Conecta tu banco, tu ERP y tu CRM en minutos, sin escribir código.',
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly stats = [
|
||||
{ value: '2.400+', label: 'Empresas activas' },
|
||||
{ value: '98%', label: 'Retención anual' },
|
||||
{ value: '12M €', label: 'Facturados cada mes' },
|
||||
{ value: '4.9/5', label: 'Valoración media' },
|
||||
];
|
||||
|
||||
protected readonly testimonials = [
|
||||
{
|
||||
quote:
|
||||
'Migramos toda la contabilidad a Nimbus en una tarde. El equipo de soporte respondió en minutos.',
|
||||
name: 'Marta Ruiz',
|
||||
role: 'COO, Cocina Litoral',
|
||||
color: 'primary' as const,
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'Por fin dejamos de perseguir números en spreadsheets. El panel en tiempo real cambió cómo tomamos decisiones.',
|
||||
name: 'Diego Ortiz',
|
||||
role: 'CEO, Bloom Studio',
|
||||
color: 'accent' as const,
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'La facturación automática nos ahorra unas 6 horas a la semana solo en el equipo de administración.',
|
||||
name: 'Laura Campos',
|
||||
role: 'Finanzas, Vela Textil',
|
||||
color: 'highlighted' as const,
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly plans: Plan[] = [
|
||||
{
|
||||
name: 'Básico',
|
||||
price: '9',
|
||||
period: '/mes',
|
||||
description: 'Para autónomos que empiezan a facturar.',
|
||||
isFeatured: false,
|
||||
features: ['Hasta 20 facturas/mes', '1 usuario', 'Soporte por email'],
|
||||
cta: 'Empezar gratis',
|
||||
},
|
||||
{
|
||||
name: 'Pro',
|
||||
price: '29',
|
||||
period: '/mes',
|
||||
description: 'Para equipos que necesitan más control.',
|
||||
isFeatured: true,
|
||||
features: [
|
||||
'Facturas ilimitadas',
|
||||
'Hasta 10 usuarios',
|
||||
'Panel de métricas en vivo',
|
||||
'Soporte prioritario',
|
||||
],
|
||||
cta: 'Empezar prueba de 14 días',
|
||||
},
|
||||
{
|
||||
name: 'Empresas',
|
||||
price: '99',
|
||||
period: '/mes',
|
||||
description: 'Para organizaciones con necesidades a medida.',
|
||||
isFeatured: false,
|
||||
features: [
|
||||
'Todo lo de Pro',
|
||||
'Usuarios ilimitados',
|
||||
'Integraciones a medida',
|
||||
'Gestor de cuenta dedicado',
|
||||
],
|
||||
cta: 'Hablar con ventas',
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly faqs: Faq[] = [
|
||||
{
|
||||
question: '¿Puedo cancelar cuando quiera?',
|
||||
answer:
|
||||
'Sí. No hay permanencia: puedes cancelar tu suscripción desde el panel en cualquier momento y seguirás teniendo acceso hasta el final del periodo pagado.',
|
||||
tagStatus: 'success',
|
||||
tag: 'Planes',
|
||||
},
|
||||
{
|
||||
question: '¿Cómo migro mis datos actuales?',
|
||||
answer:
|
||||
'Importamos clientes, facturas y catálogo desde una hoja de cálculo o desde tu herramienta anterior. El equipo de soporte te acompaña durante todo el proceso.',
|
||||
tagStatus: 'pending',
|
||||
tag: 'Migración',
|
||||
},
|
||||
{
|
||||
question: '¿Mis datos están seguros?',
|
||||
answer:
|
||||
'Toda la información viaja cifrada y se aloja en servidores dentro de la UE, con copias de seguridad diarias.',
|
||||
tagStatus: 'info',
|
||||
tag: 'Seguridad',
|
||||
},
|
||||
{
|
||||
question: '¿Ofrecéis soporte en español?',
|
||||
answer:
|
||||
'Sí, todo nuestro equipo de soporte atiende en español por chat y email, con un tiempo de respuesta medio de menos de una hora en horario laboral.',
|
||||
tagStatus: 'info',
|
||||
tag: 'Soporte',
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly expandedFaqIndex = signal<number | null>(0);
|
||||
}
|
||||
92
apps/playground/src/app/pages/examples/login/login.page.html
Normal file
92
apps/playground/src/app/pages/examples/login/login.page.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<div class="login">
|
||||
<section class="login__brand">
|
||||
<div class="login__brand-content">
|
||||
<div class="login__logo">
|
||||
<pi-icon icon="dashboard" color="white" size="large" />
|
||||
<span>Nimbus</span>
|
||||
</div>
|
||||
|
||||
<h1>Todo el control de tu negocio, en un solo panel.</h1>
|
||||
<p>
|
||||
Facturación, clientes y métricas en tiempo real. Nimbus conecta tus
|
||||
herramientas para que dejes de perseguir números en hojas de cálculo.
|
||||
</p>
|
||||
|
||||
<ul class="login__highlights">
|
||||
@for (item of highlights; track item.text) {
|
||||
<li>
|
||||
<pi-icon [icon]="item.icon" color="white" size="small" />
|
||||
<span>{{ item.text }}</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
<pi-card
|
||||
alignment="flex-start"
|
||||
gap="s"
|
||||
innerPadding="m"
|
||||
context="public"
|
||||
color="white"
|
||||
[hasShadow]="true"
|
||||
class="login__quote"
|
||||
>
|
||||
<pi-card-center-content>
|
||||
<p>
|
||||
«Migramos toda la contabilidad a Nimbus en una tarde. El equipo de
|
||||
soporte respondió en minutos.»
|
||||
</p>
|
||||
</pi-card-center-content>
|
||||
<pi-card-bottom-content>
|
||||
<div class="login__quote-author">
|
||||
<pi-avatar name="Marta Ruiz" color="primary" size="small" />
|
||||
<div>
|
||||
<strong>Marta Ruiz</strong>
|
||||
<span>COO, Cocina Litoral</span>
|
||||
</div>
|
||||
</div>
|
||||
</pi-card-bottom-content>
|
||||
</pi-card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="login__form-side">
|
||||
<pi-card
|
||||
alignment="stretch"
|
||||
gap="l"
|
||||
innerPadding="xl"
|
||||
context="public"
|
||||
color="white"
|
||||
class="login__form-card"
|
||||
>
|
||||
<pi-card-top-content>
|
||||
<h2>Inicia sesión</h2>
|
||||
<p class="login__form-hint">
|
||||
¿No tienes cuenta todavía?
|
||||
<a href="javascript:void(0)">Prueba Nimbus gratis</a>
|
||||
</p>
|
||||
</pi-card-top-content>
|
||||
|
||||
<pi-card-center-content>
|
||||
<form class="login__form" (submit)="$event.preventDefault(); submit()">
|
||||
<pi-email-field [formField]="f.email" placeholder="tu@empresa.com">
|
||||
<pi-label>Email</pi-label>
|
||||
</pi-email-field>
|
||||
|
||||
<pi-password-field [formField]="f.password" placeholder="••••••••">
|
||||
<pi-label>Contraseña</pi-label>
|
||||
</pi-password-field>
|
||||
|
||||
<div class="login__form-row">
|
||||
<label class="login__remember">
|
||||
<pi-slide-toggle [formField]="f.remember" />
|
||||
<span>Recuérdame</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)">¿Olvidaste tu contraseña?</a>
|
||||
</div>
|
||||
|
||||
<button pi-primary w100 large type="submit">Entrar</button>
|
||||
</form>
|
||||
</pi-card-center-content>
|
||||
</pi-card>
|
||||
</section>
|
||||
</div>
|
||||
179
apps/playground/src/app/pages/examples/login/login.page.scss
Normal file
179
apps/playground/src/app/pages/examples/login/login.page.scss
Normal file
@@ -0,0 +1,179 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
|
||||
min-height: 80vh;
|
||||
border-radius: 1.25rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 30px 60px -30px rgba(20, 30, 70, 0.25);
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.login {
|
||||
grid-template-columns: 1fr;
|
||||
min-height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Panel de marca ---------- */
|
||||
|
||||
.login__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--pi-spacing-xxxx-large);
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 20% 20%,
|
||||
rgba(255, 255, 255, 0.14),
|
||||
transparent 45%
|
||||
),
|
||||
linear-gradient(
|
||||
145deg,
|
||||
var(--pi-color-primary-900),
|
||||
var(--pi-color-highlighted)
|
||||
);
|
||||
color: var(--pi-color-white);
|
||||
}
|
||||
|
||||
.login__brand-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-x-large);
|
||||
max-width: 26rem;
|
||||
}
|
||||
|
||||
.login__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-small);
|
||||
font-size: var(--pi-font-size-large);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.login__brand-content h1 {
|
||||
margin: 0;
|
||||
font-size: var(--pi-font-size-xx-large);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.login__brand-content > p {
|
||||
margin: 0;
|
||||
color: var(--pi-color-primary-100);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.login__highlights {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-small);
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-small);
|
||||
font-size: var(--pi-font-size-small);
|
||||
}
|
||||
}
|
||||
|
||||
.login__quote {
|
||||
margin-top: var(--pi-spacing-small);
|
||||
background: rgba(255, 255, 255, 0.08) !important;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
color: var(--pi-color-white);
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.login__quote-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-small);
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
font-size: var(--pi-font-size-x-small);
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
font-size: var(--pi-font-size-xxx-small);
|
||||
color: var(--pi-color-primary-100);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Panel de formulario ---------- */
|
||||
|
||||
.login__form-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--pi-spacing-xxxx-large) var(--pi-spacing-x-large);
|
||||
background: var(--pi-color-white);
|
||||
}
|
||||
|
||||
.login__form-card {
|
||||
width: 100%;
|
||||
max-width: 24rem;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.login__form-card h2 {
|
||||
margin: 0 0 var(--pi-spacing-xxx-small);
|
||||
font-size: var(--pi-font-size-x-large);
|
||||
color: var(--pg-page-text);
|
||||
}
|
||||
|
||||
.login__form-hint {
|
||||
margin: 0;
|
||||
color: var(--pg-text-muted);
|
||||
font-size: var(--pi-font-size-small);
|
||||
|
||||
a {
|
||||
color: var(--pi-color-primary);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--pi-spacing-large);
|
||||
}
|
||||
|
||||
.login__form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--pi-spacing-small);
|
||||
font-size: var(--pi-font-size-small);
|
||||
|
||||
a {
|
||||
color: var(--pi-color-primary);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login__remember {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pi-spacing-xx-small);
|
||||
color: var(--pg-text-muted);
|
||||
}
|
||||
68
apps/playground/src/app/pages/examples/login/login.page.ts
Normal file
68
apps/playground/src/app/pages/examples/login/login.page.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
||||
import { FormField, email, form } from '@angular/forms/signals';
|
||||
|
||||
import {
|
||||
PiAvatarComponent,
|
||||
PiCardBottomContentDirective,
|
||||
PiCardCenterContentDirective,
|
||||
PiCardComponent,
|
||||
PiCardTopContentDirective,
|
||||
PiColorPrimaryDirective,
|
||||
PiEmailFieldComponent,
|
||||
PiIconComponent,
|
||||
PiLabelDirective,
|
||||
PiLargeButtonDirective,
|
||||
PiPasswordFieldComponent,
|
||||
PiSlideToggleComponent,
|
||||
PiW100ButtonDirective,
|
||||
} from '@pi/ui';
|
||||
|
||||
interface LoginModel {
|
||||
email: string;
|
||||
password: string;
|
||||
remember: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './login.page.html',
|
||||
styleUrl: './login.page.scss',
|
||||
imports: [
|
||||
FormField,
|
||||
PiAvatarComponent,
|
||||
PiCardBottomContentDirective,
|
||||
PiCardCenterContentDirective,
|
||||
PiCardComponent,
|
||||
PiCardTopContentDirective,
|
||||
PiColorPrimaryDirective,
|
||||
PiEmailFieldComponent,
|
||||
PiIconComponent,
|
||||
PiLabelDirective,
|
||||
PiLargeButtonDirective,
|
||||
PiPasswordFieldComponent,
|
||||
PiSlideToggleComponent,
|
||||
PiW100ButtonDirective,
|
||||
],
|
||||
})
|
||||
export class LoginPage {
|
||||
protected readonly highlights = [
|
||||
{ icon: 'check-rounded', text: 'Reportes en tiempo real, sin esperas' },
|
||||
{ icon: 'check-rounded', text: 'Integra tu stack en menos de 10 minutos' },
|
||||
{ icon: 'check-rounded', text: 'Soporte humano, siempre en español' },
|
||||
];
|
||||
|
||||
private readonly model = signal<LoginModel>({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: true,
|
||||
});
|
||||
|
||||
protected readonly f = form(this.model, (m) => {
|
||||
email(m.email, { message: 'Introduce un email válido' });
|
||||
});
|
||||
|
||||
protected submit(): void {
|
||||
this.f().markAsTouched();
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,47 @@ html.dark-mode {
|
||||
--pi-color-background-300: #4b505c;
|
||||
--pi-color-background-400: #1c1f26;
|
||||
--pi-color-background-500: #181b22;
|
||||
|
||||
// --- @pi/ui: accordion/tab ---------------------------------------------
|
||||
// Ambos usan un escalón claro de la paleta de marca (`--pi-color-{color}-25`
|
||||
// / `-50`) como fondo de toda la superficie, no como un simple acento — así
|
||||
// que, a diferencia de un badge o un chip, no puede quedarse claro en
|
||||
// oscuro. Los tokens `--pi-theme-accordion-surface-*` / `--pi-theme-tab-*`
|
||||
// (con fallback al valor de marca de siempre) permiten oscurecer solo estas
|
||||
// dos superficies sin tocar `--pi-color-*` y sin afectar a nada que sí
|
||||
// funciona bien en oscuro (chips, badges...).
|
||||
--pi-theme-accordion-surface-primary: var(--pi-color-grey-50);
|
||||
--pi-theme-accordion-surface-primary-expanded: var(--pi-color-grey-100);
|
||||
--pi-theme-accordion-surface-warn: var(--pi-color-grey-50);
|
||||
--pi-theme-accordion-surface-warn-expanded: var(--pi-color-grey-100);
|
||||
--pi-theme-accordion-surface-accent: var(--pi-color-grey-50);
|
||||
--pi-theme-accordion-surface-accent-expanded: var(--pi-color-grey-100);
|
||||
|
||||
--pi-theme-tab-header-surface: var(--pi-color-grey-50);
|
||||
--pi-theme-tab-header-surface-fill: var(--pi-color-grey-50);
|
||||
--pi-theme-tab-active-surface: var(--pi-color-grey-100);
|
||||
|
||||
// --- @pi/ui: select-option ---------------------------------------------
|
||||
// Mismo caso: cada fila de opción usa un escalón claro de marca como fondo
|
||||
// de toda la fila (no un acento), en 3 estados (reposo/seleccionada/hover).
|
||||
// Un solo token oscuro por estado basta — todos los colores de marca
|
||||
// (primary/accent/highlighted/success/warn) comparten el mismo criterio de
|
||||
// superficie en oscuro, aunque conserven su matiz en claro.
|
||||
--pi-theme-select-option-surface-primary: var(--pi-color-grey-50);
|
||||
--pi-theme-select-option-surface-primary-selected: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-primary-hover: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-accent: var(--pi-color-grey-50);
|
||||
--pi-theme-select-option-surface-accent-selected: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-accent-hover: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-highlighted: var(--pi-color-grey-50);
|
||||
--pi-theme-select-option-surface-highlighted-selected: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-highlighted-hover: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-success: var(--pi-color-grey-50);
|
||||
--pi-theme-select-option-surface-success-selected: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-success-hover: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-warn: var(--pi-color-grey-50);
|
||||
--pi-theme-select-option-surface-warn-selected: var(--pi-color-grey-100);
|
||||
--pi-theme-select-option-surface-warn-hover: var(--pi-color-grey-100);
|
||||
}
|
||||
|
||||
// El tema alternativo usa escalones claros de su marca como superficie de los
|
||||
|
||||
Reference in New Issue
Block a user