Librería de componentes moderna y elegante para Angular. Diseñada para ofrecer una experiencia premium, cada componente ha sido cuidadosamente elaborado con Material Design 3, micro-animaciones fluidas y un sistema de temas dinámico.
Instala el paquete principal y sus dependencias de diseño:
npm install nebula-ui-kit
Nota: Se recomienda el uso de Angular v21 para aprovechar al máximo el sistema de Signals y Model inputs.
import { ButtonComponent, InputComponent } from 'nebula-ui-kit';
@Component({
standalone: true,
imports: [ButtonComponent, InputComponent],
template: `
<lib-input label="Username" placeholder="Enter username..."></lib-input>
<lib-button (clicked)="onSubmit()">Submit</lib-button>
`
})
export class MyComponent {}
Incluye el tema en tu styles.scss y la fuente Open Sans en tu index.html:
@use "nebula-ui-kit/styles/theme";
Nebula permite centralizar textos y comportamientos comunes mediante el token NEBULA_UI_CONFIG.
Provee el token en tu app.config.ts:
import { ApplicationConfig } from '@angular/core';
import { NEBULA_UI_CONFIG } from 'nebula-ui-kit';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: NEBULA_UI_CONFIG,
useValue: {
requiredMessage: '¡Este campo es obligatorio!',
selectPlaceholder: 'Seleccione una opción',
datepickerLabel: 'Fecha de nacimiento',
datepickerPlaceholder: 'DD/MM/AAAA',
tableEmptyDash: 'N/A',
locale: 'es-CO',
getConnectionStatus: () => !!localStorage.getItem('user_data'),
alertDurations: {
success: 3000,
warning: 4000,
error: 5000,
info: 0 // Persistente
},
navStorageKey: 'my_custom_nav_v1', // Opcional: Nombre personalizado en localStorage
encryptionKey: 'nebula-secret-key-2026' // Opcional: Habilita cifrado XOR
}
}
]
};
Nebula proporciona dos formas de registrar iconos. Se recomienda usar el set Core para aplicaciones de negocio.
Registra automáticamente el set esencial de Nébula (Layout, ERP, Finanzas):
import { provideNebulaCoreIcons } from 'nebula-ui-kit';
// app.config.ts
providers: [
provideNebulaCoreIcons(),
]
Para añadir iconos específicos de Lucide que no están en el set core:
import { provideNebulaIcons } from 'nebula-ui-kit';
import { Truck, Ship } from 'lucide-angular/icons';
// app.config.ts
providers: [
provideNebulaIcons({ Truck, Ship }),
]
Para formateo manual de datos, puede inyectar el FormatService que incluye utilidades para moneda, números y fechas estandarizadas:
import { FormatService } from 'nebula-ui-kit';
export class MyComponent {
private _format = inject(FormatService);
// Formateo de moneda (COP por defecto)
price = this._format.formatValue(1500, { key: 'p', label: 'P', pipe: 'currency' });
// Formateo de fecha estandarizado
today = this._format.formatDate(new Date(), true); // con hora
}
lib-input: Campos de texto con soporte para iconos, prefijos y sufijos.lib-select: Selectores con búsqueda integrada y Signals.lib-textarea: Áreas de texto con auto-ajuste dinámico.lib-datepicker: Selector de fecha con calendario moderno.lib-checkbox & lib-radio: Controles de selección (Radio soporta label para agrupar).lib-file-upload: Zona de carga de archivos con validaciones y soporte para selección múltiple.lib-button: Botones premium con estados de carga e integración de iconos.lib-alert: Notificaciones visuales premium con soporte para title, message y estados semánticos. Incluye auto-cierre inteligente configurable por tipo o instancia.lib-table: Tabla con ordenamiento, paginación y filtrado interno/externo reactivo.lib-tabs: Sistema de pestañas para gestión de navegación y áreas de trabajo dinámicas.lib-gridcard: Contenedor responsivo para mostrar una rejilla de tarjetas.lib-badge: Pequeños indicadores de estado o etiquetas con variantes de color.lib-skeleton: Efectos de carga suaves para evitar saltos visuales.lib-empty: Estados vacíos con soporte para iconos, mensajes y acciones.lib-icon: Integración optimizada con Lucide Icons. Incluye un set pre-configurado de +80 iconos de negocio (ERP, Finanzas y Contabilidad) listos para usar.lib-sidebar: Menú lateral colapsable con soporte para items anidados y estados activos.lib-header: Barra de navegación superior con soporte para logo, búsqueda, acciones y perfil de usuario.lib-breadcrumb: Barra de migas de pan dinámica para navegación jerárquica con soporte para iconos y estados activos.lib-footer: Pie de página unificado con variantes (default, login) y estados de conexión.lib-search: Componente de búsqueda con debounce integrado y Signals.lib-confirm-dialog: Diálogos de confirmación rápidos con tipos semánticos.libDialogHeader, libDialogBody, lib-dialog-footer: Componentes y directivas para estructurar diálogos. El Footer soporta navegación multi-paso mediante el input multiStep y el output backAction.DialogService: Servicio para abrir cualquier componente como modal programático.NebulaNavigationService: Servicio centralizado para gestionar el estado de pestañas (tabs) y navegación reactiva. Incluye persistencia automática en localStorage y soporte para múltiples contextos (módulos).nebulaTabGuard: Guard de ruta funcional que asegura que las URLs profundas solo sean accesibles si la pestaña correspondiente está abierta en la sesión activa.Muchos componentes exponen el input ariaLabel para casos donde no hay texto visible (ej. botones de icono):
<lib-button icon="Search" ariaLabel="Search records" />
<lib-icon name="Info" ariaLabel="More information about the process" />
Siguiendo los estándares de la arquitectura Nébula, todas las clases de estado y estilos base deben declararse en el decorador @Component para mantener el template limpio y desacoplado:
@Component({
selector: 'lib-custom-el',
host: {
'class': 'lib-custom-el',
'[class.is-active]': 'active()',
'[attr.aria-disabled]': 'disabled()',
'[attr.aria-label]': 'ariaLabel() || null'
}
})
export class CustomComponent {
active = input(false);
disabled = input(false);
ariaLabel = input<string>('');
}
Siempre proporciona un ariaLabel para elementos interactivos que carecen de etiquetas de texto visibles.
<lib-button
variant="tonal"
icon="Trash2"
ariaLabel="Delete selected record"
></lib-button>
<!-- Los iconos decorativos están automáticamente ocultos de los lectores de pantalla (aria-hidden="true") -->
<lib-icon name="Smile"></lib-icon>
Usa skeletons como pares de tus componentes reales:
<div class="field-container">
@if (isLoading()) {
<lib-skeleton variant="rectangle" height="56px" borderRadius="12px"></lib-skeleton>
} @else {
<lib-input label="Full Name" [(value)]="fullName"></lib-input>
}
</div>
El GridCardComponent ya incluye soporte interno para skeletons mediante el input loading:
<lib-gridcard
[items]="myData()"
[loading]="isDataLoading()"
></lib-gridcard>
lib-gridcard aplica una lógica visual estricta para asegurar la legibilidad de métricas críticas cuando type === 'scorecard':
{
title: 'Ventas Útiles',
value: '84%',
type: 'scorecard',
statusColor: 'success',
iconPosition: 'right'
}
Para asegurar la paridad visual absoluta, algunos componentes como lib-card gestionan su propio estado de carga internamente:
<lib-card
title="Project Alpha"
subtitle="Active"
[loading]="isCardLoading()"
></lib-card>
Para implementar búsquedas en tiempo real, combina computed() con inputs de búsqueda. Este patrón asegura que la UI se actualice automáticamente sin necesidad de ciclos de vida manuales:
export class UserListComponent {
users = signal<User[]>([]);
query = signal('');
// El estado derivado se recalcula automáticamente cuando query() o users() cambian
filteredUsers = computed(() => {
const term = this.query().toLowerCase();
return this.users().filter(u =>
u.name.toLowerCase().includes(term) ||
u.email.toLowerCase().includes(term)
);
});
}
<lib-search [(searchTerm)]="query" placeholder="Filter users..."></lib-search>
<lib-table [data]="filteredUsers()" [config]="tableConfig"></lib-table>
El componente lib-table utiliza renderizado fixed y el truncamiento de textos largos text-overflow: ellipsis. Para gestionar que columnas "delgadas" debes configurar explícitamente la propiedad width en las columnas predecibles dentro de tu ColumnConfig. Las columnas que no declaren explícitamente un ancho se repartirán uniformemente el espacio restante.
const columns: ColumnConfig<UserData>[] = [
{ key: 'id', label: 'ID', width: '80px', align: 'center' },
{ key: 'status', label: 'Estado', width: '120px' },
{ key: 'email', label: 'Correo', width: '35%' },
{ key: 'name', label: 'Nombre Completo' },
];
El TableComponent utiliza un model para el término de búsqueda, lo que facilita el filtrado reactivo bidireccional:
<lib-input
label="Search records"
icon="Search"
[(value)]="tableSearch"
></lib-input>
<lib-table
[data]="users()"
[config]="tableConfig"
[(searchTerm)]="tableSearch"
></lib-table>
El SearchComponent utiliza el model de Angular para el enlace bidireccional y proporciona un mecanismo de debounce incorporado. También incluye una propiedad loading para mostrar un icono giratorio durante las operaciones asíncronas.
<lib-search
[(searchTerm)]="query"
[debounceMs]="300"
[loading]="isTableLoading()"
(searchChange)="onSearch($event)"
placeholder="Search users..."
></lib-search>
El SidebarComponent es totalmente reactivo y gestiona su estado de expansión a través del input collapsed y el output collapsedChange, lo que permite a los componentes padres sincronizar el estado fácilmente.
<!-- app.component.html -->
<lib-sidebar
[menuItems]="navItems()"
[(collapsed)]="sidebarCollapsed"
></lib-sidebar>
<lib-header
[user]="'Example User'"
[isDarkMode]="isDark()"
(darkModeToggle)="toggleTheme()"
(search)="onSearch($event)"
></lib-header>
<lib-breadcrumb
[items]="[
{ label: 'Finance', route: '/finance' },
{ label: 'Accounts Payable', route: '/finance/ap' },
{ label: 'Create Order' }
]"
></lib-breadcrumb>
Nebula facilita que abra "espacios de trabajo" en pestañas mediante la combinación del SidebarComponent, TabsComponent y el NebulaNavigationService.
@Component({
template: `
<lib-sidebar
[menuItems]="menu"
[activeItemIds]="navService.openedRoutes()"
(itemClick)="navService.addTabFromMenu($event)"
/>
<main>
<lib-tabs
[tabs]="navService.tabs()"
[activeId]="navService.activeTabId()"
(tabChange)="navService.selectTab($event)"
(onClose)="navService.closeTab($event)"
/>
<router-outlet />
</main>
`
})
export class AppComponent {
navService = inject(NebulaNavigationService);
}
El FooterComponent soporta diferentes variantes de diseño y un indicador de estado de conexión dinámico, centralizado a través del NEBULA_UI_CONFIG o pasado como inputs.
<lib-footer
variant="default"
[status]="'Online'"
[date]="today"
></lib-footer>
El AlertComponent gestiona su visibilidad internamente pero emite un evento closed para permitir a los componentes padres limpiar el DOM a través de @if.
@if (isAlertVisible()) {
<lib-alert
type="success"
title="Action Result"
message="Data saved successfully"
[autoClose]="true"
[duration]="3000"
(closed)="isAlertVisible.set(false)"
></lib-alert>
}
El componente TabsComponent ahora soporta una API declarativa con Lazy Loading. El contenido envuelto en libTabContent solo se instanciará cuando la pestaña esté activa.
export class MainContainer {
activeWorkspace = signal<string | number>('orders');
onTabClose(id: string | number) {
// Lógica para cerrar pestaña
}
onSave() {
console.log('Action confirmed from inside the tab!');
}
}
<lib-tabs [(activeId)]="activeWorkspace" (onClose)="onTabClose($event)">
<!-- Pestaña declarativa con carga diferida -->
<lib-tab
id="orders"
title="Payment Orders"
[closable]="true"
(onConfirm)="onSave()">
<ng-template libTabContent>
<lib-order-form /> <!-- Solo se carga cuando está activo -->
</ng-template>
</lib-tab>
<lib-tab id="invoices" title="Invoices" route="/finance/invoices">
<ng-template libTabContent>
<lib-invoice-list />
</ng-template>
</lib-tab>
</lib-tabs>
El DialogService permite abrir componentes como modales. Para diálogos de confirmación rápidos (estilo alerta), use el ConfirmDialogComponent.
El DialogService proporciona un método directo para confirmaciones rápidas que devuelve un Observable<boolean>:
this.dialogService.confirm({
title: 'Delete invoice?',
message: 'This action cannot be undone.',
variant: 'error',
confirmLabel: 'Delete',
cancelLabel: 'Cancel'
}).subscribe(confirmed => {
if (confirmed) {
console.log('Delete confirmed');
}
});
Utiliza las directivas estructurales para mantener la consistencia:
<div libDialogHeader>
<h3>New Record</h3>
</div>
<div libDialogBody>
<lib-input label="Full Name" />
</div>
<lib-dialog-footer [multiStep]="isStep2" (backAction)="goToStep1()">
<lib-button variant="secondary" (clicked)="close()">Cancel</lib-button>
<lib-button (clicked)="save()">{{ isStep2 ? 'Finish' : 'Next' }}</lib-button>
</lib-dialog-footer>
El FileUploadComponent combina un botón de selección nativo con una zona de drag & drop. Emite filesSelected con los archivos válidos y fileSizeError cuando se supera el límite configurado.
<lib-file-upload
label="Attach payment support"
hint="Only XLSX or CSV files, maximum 5 MB."
accept=".xlsx,.csv"
[multiple]="true"
[maxSizeBytes]="5 * 1024 * 1024"
(filesSelected)="onFiles($event)"
(fileSizeError)="showError($event)"
></lib-file-upload>
export class PaymentFormComponent {
onFiles(files: UploadedFile[]) {
console.log('Valid files:', files.map(f => f.name));
}
showError(msg: string) {
console.error(msg); // "archivo.xlsx" supera el tamaño máximo de 5 MB.
}
}
Siempre utiliza una propiedad única (como id) en la expresión track de tus bucles @for para elevar el rendimiento de renderizado y mantener el estado del DOM (foco, animaciones, scroll).
<!-- RECOMENDADO: Usar propiedades únicas sustentables -->
@for (item of data(); track item.id ?? $index) {
<div class="list-item">{{ item.name }}</div>
}
Nebula utiliza un sistema de Design Tokens basado en variables CSS para una personalización instantánea.
Define estas variables en tu :root antes de cargar el tema:
:root {
/* Tipografía y bordes */
--lib-border-radius: 8px;
--lib-font-family: 'Inter', sans-serif;
/* Espaciados (Spacing) */
--lib-spacing-xs: 4px;
--lib-spacing-sm: 8px;
--lib-spacing-md: 16px;
--lib-spacing-lg: 24px;
--lib-spacing-xl: 32px;
}
Puedes inyectar tu propia paleta de colores usando el mixin oficial:
@use '@angular/material' as mat;
@use "nebula-ui-kit/styles/theme" as nebula;
@include nebula.apply-nebula-theme(
$primary: mat.$azure-palette
);
color-mix()Para sombras, estados de interacción (hover/focus) y capas de cristal (glassmorphism), la librería recomienda el uso de color-mix sobre los tokens de Material 3. Esto asegura que los efectos visuales se adapten automáticamente al brillo del tema (Light/Dark) sin hardcodear colores:
/* Ejemplo de hover reactivo al tema */
.my-card {
background: var(--md-sys-color-surface);
transition: background 0.2s ease;
&:hover {
/* Mezcla el color de superficie con el color de texto (on-surface) al 5% */
background: color-mix(in srgb, var(--md-sys-color-surface), var(--md-sys-color-on-surface) 5%);
}
}
/* Ejemplo de elevación (Sombra adaptativa) */
.premium-panel {
box-shadow: 0 8px 32px color-mix(in srgb, var(--md-sys-color-on-surface), transparent 90%);
}
/* Ejemplo de interacción tonal en Botones */
.lib-btn-tonal {
background: var(--md-sys-color-error-container);
color: var(--md-sys-color-on-error-container);
&:hover {
/* Aclara sutilmente el fondo para feedback visual */
background: color-mix(in srgb, var(--md-sys-color-error-container), white 8%);
}
&:active {
/* Oscurece mezclando con el color del texto */
background: color-mix(in srgb, var(--md-sys-color-error-container), var(--md-sys-color-on-error-container) 16%);
}
}
Nebula recomienda un modelo donde el Sidebar abre Pestañas (Tabs) y las rutas profundas están protegidas por el estado de navegación en localStorage.
Provee el NebulaNavigationService en el root para que el estado persista entre cambios de módulo.
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
NebulaNavigationService,
// ...
]
};
nebulaTabGuardAsegura que los usuarios no puedan acceder a URLs profundas si la pestaña no está abierta.
// app.routes.ts
import { nebulaTabGuard } from 'nebula-ui-kit';
import { authGuard } from './core/guards/auth.guard'; // Ejemplo de guard de autenticación
export const routes: Routes = [
{
path: 'accounting',
// IMPORTANTE: Primero autenticar, luego validar si la pestaña está abierta
canActivate: [authGuard, nebulaTabGuard],
children: [
{ path: 'ledger', component: LedgerComponent }
]
}
];
Los componentes lib-sidebar y lib-tabs detectan automáticamente el NebulaNavigationService para sincronizar su estado sin necesidad de inputs manuales.
@Component({
template: `
<!-- Sidebar: detecta rutas abiertas y gestiona clics -->
<lib-sidebar [menuItems]="activeMenu()" />
<main>
<!-- Tabs: sincronizadas con el servicio y el router -->
<lib-tabs />
<router-outlet />
</main>
`
})
export class MainLayout {
// Opcional: Sidebar y Tabs ya se sincronizan automáticamente, inyécta si necesitas control manual
private navService = inject(NebulaNavigationService);
}
Copyright © 2026 Equipo de Nébula. Todos los derechos reservados.