codexly-ui 0.10.113 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -627,6 +627,28 @@ class ClxThemeService {
627
627
  bordered: t?.bordered ?? true,
628
628
  };
629
629
  }, ...(ngDevMode ? [{ debugName: "paginationStyle" }] : /* istanbul ignore next */ []));
630
+ /** Resolved style for ClxNotificationComponent — severity colors fall back to built-in
631
+ * green/amber/sky/red, unreadColor to primaryColor, borderColor to listBorderColor then
632
+ * primaryColor, radius to the app-wide borderRadius. Per-instance inputs on clx-notification
633
+ * still take precedence over this theme-wide default. */
634
+ notificationStyle = computed(() => {
635
+ const n = this._config().notification;
636
+ return {
637
+ severityColors: {
638
+ success: n?.severityColors?.success ?? this._config().successColor,
639
+ warning: n?.severityColors?.warning ?? this._config().warningColor,
640
+ info: n?.severityColors?.info ?? 'sky',
641
+ error: n?.severityColors?.error ?? this._config().dangerColor,
642
+ },
643
+ unreadColor: n?.unreadColor ?? this._config().primaryColor,
644
+ size: n?.size ?? 'md',
645
+ borderColor: n?.borderColor ?? this._config().listBorderColor ?? this._config().primaryColor,
646
+ radius: n?.radius ?? this._config().borderRadius,
647
+ titleWeight: n?.titleWeight ?? 600,
648
+ detailColor: n?.detailColor,
649
+ groupLabelWeight: n?.groupLabelWeight ?? 700,
650
+ };
651
+ }, ...(ngDevMode ? [{ debugName: "notificationStyle" }] : /* istanbul ignore next */ []));
630
652
  /** Resolved text color for solid primary/secondary buttons — undefined means "let
631
653
  * resolveColor() auto-pick white/dark-800 based on shade", same as today. */
632
654
  buttonTextColor(role) {
@@ -9581,6 +9603,13 @@ class ClxTabsComponent {
9581
9603
  _clxColor = computed(() => this.clxColor() ?? this._themeSvc.config().primaryColor, ...(ngDevMode ? [{ debugName: "_clxColor" }] : /* istanbul ignore next */ []));
9582
9604
  _panels = contentChildren(ClxTabDirective, ...(ngDevMode ? [{ debugName: "_panels" }] : /* istanbul ignore next */ []));
9583
9605
  _underlineClass = computed(() => resolveColor(this._clxColor()).bg, ...(ngDevMode ? [{ debugName: "_underlineClass" }] : /* istanbul ignore next */ []));
9606
+ _badgeClass(tabId) {
9607
+ const active = this.activeTab() === tabId;
9608
+ const t = resolveColor(this._clxColor());
9609
+ return active
9610
+ ? `ml-0.5 px-1.5 py-0.5 rounded-full text-[10px] font-bold leading-none tabular-nums ${t.bgSubtle} ${t.textSubtle}`
9611
+ : 'ml-0.5 px-1.5 py-0.5 rounded-full text-[10px] font-bold leading-none tabular-nums bg-clx-surface-2 text-clx-text-subtle';
9612
+ }
9584
9613
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9585
9614
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxTabsComponent, isStandalone: true, selector: "clx-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, activeTab: { classPropertyName: "activeTab", publicName: "activeTab", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTab: "activeTabChange" }, queries: [{ propertyName: "_panels", predicate: ClxTabDirective, isSignal: true }], ngImport: i0, template: `
9586
9615
  <!-- Tab bar -->
@@ -9603,6 +9632,9 @@ class ClxTabsComponent {
9603
9632
  <span clx-icon [name]="tab.icon" size="xs" class="shrink-0"></span>
9604
9633
  }
9605
9634
  {{ tab.label }}
9635
+ @if (tab.badge !== undefined) {
9636
+ <span [class]="_badgeClass(tab.id)">{{ tab.badge }}</span>
9637
+ }
9606
9638
  </button>
9607
9639
  @if (activeTab() === tab.id) {
9608
9640
  <span class="absolute bottom-0 left-0 right-0 h-0.5 rounded-full {{ _underlineClass() }}"></span>
@@ -9644,6 +9676,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
9644
9676
  <span clx-icon [name]="tab.icon" size="xs" class="shrink-0"></span>
9645
9677
  }
9646
9678
  {{ tab.label }}
9679
+ @if (tab.badge !== undefined) {
9680
+ <span [class]="_badgeClass(tab.id)">{{ tab.badge }}</span>
9681
+ }
9647
9682
  </button>
9648
9683
  @if (activeTab() === tab.id) {
9649
9684
  <span class="absolute bottom-0 left-0 right-0 h-0.5 rounded-full {{ _underlineClass() }}"></span>
@@ -11236,6 +11271,375 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
11236
11271
  }]
11237
11272
  }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], showClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClose", required: false }] }], confirmButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmButton", required: false }] }], cancelButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelButton", required: false }] }], showCancelButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCancelButton", required: false }] }], confirmClick: [{ type: i0.Output, args: ["confirmClick"] }], cancelClick: [{ type: i0.Output, args: ["cancelClick"] }] } });
11238
11273
 
11274
+ // ── Size map — same density-token shape as LIST_SIZE_MAP ──────────────────────
11275
+ const NOTIFICATION_SIZE_MAP = {
11276
+ sm: { row: 'py-2', iconWrap: 'w-7 h-7', iconSize: 'xs', title: 'text-xs', detail: 'text-[11px]', time: 'text-[10px]' },
11277
+ md: { row: 'py-2.5', iconWrap: 'w-8.5 h-8.5', iconSize: 'sm', title: 'text-[13px]', detail: 'text-xs', time: 'text-[11px]' },
11278
+ lg: { row: 'py-3', iconWrap: 'w-10 h-10', iconSize: 'md', title: 'text-sm', detail: 'text-[13px]', time: 'text-xs' },
11279
+ };
11280
+ // ── Default icon per severity — used when an item doesn't specify its own `icon` ──
11281
+ const NOTIFICATION_SEVERITY_ICON = {
11282
+ success: 'check_circle',
11283
+ warning: 'notifications_active',
11284
+ info: 'info',
11285
+ error: 'error',
11286
+ };
11287
+
11288
+ /** Self-contained bell trigger + floating panel — same overlay pattern as clx-profile (CDK
11289
+ * Overlay anchored to the trigger, no page-level backdrop), not a full-height clx-drawer. It's
11290
+ * "dumb" beyond that: renders whatever `items()` it's given and emits intent (itemClick/
11291
+ * markRead/delete/...) outward. Callers (e.g. a NotificationService backed by SSE) own fetching
11292
+ * and persistence — this component never calls an API itself. */
11293
+ class ClxNotificationComponent {
11294
+ _themeSvc = inject(ClxThemeService);
11295
+ items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
11296
+ color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
11297
+ /** Optional third tab that filters items by ClxNotificationItem.category — e.g.
11298
+ * `{ id: 'orders', label: 'Órdenes' }`. Omit for just Todas/Sin leer. */
11299
+ categoryTab = input(undefined, ...(ngDevMode ? [{ debugName: "categoryTab" }] : /* istanbul ignore next */ []));
11300
+ itemClick = output();
11301
+ markRead = output();
11302
+ markAllRead = output();
11303
+ delete = output();
11304
+ deleteAllRead = output();
11305
+ _style = this._themeSvc.notificationStyle;
11306
+ _color = computed(() => this.color() ?? this._themeSvc.config().primaryColor, ...(ngDevMode ? [{ debugName: "_color" }] : /* istanbul ignore next */ []));
11307
+ // ── CDK Overlay ─────────────────────────────────────────────────────────────
11308
+ _sso = inject(ScrollStrategyOptions);
11309
+ _scrollStrategy = this._sso.reposition();
11310
+ _positions = [
11311
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 8 },
11312
+ { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -8 },
11313
+ ];
11314
+ _isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : /* istanbul ignore next */ []));
11315
+ _filter = signal('all', ...(ngDevMode ? [{ debugName: "_filter" }] : /* istanbul ignore next */ []));
11316
+ _ringClass = computed(() => {
11317
+ const t = resolveColor(this._color());
11318
+ return `focus-visible:${t.ringBase}`;
11319
+ }, ...(ngDevMode ? [{ debugName: "_ringClass" }] : /* istanbul ignore next */ []));
11320
+ _panelClass = computed(() => `w-96 bg-clx-surface shadow-xl border border-clx-border-soft overflow-hidden mt-2 flex flex-col ${resolveContainerRadius(this._style().radius)}`, ...(ngDevMode ? [{ debugName: "_panelClass" }] : /* istanbul ignore next */ []));
11321
+ _unreadCount = computed(() => this.items().filter(i => !i.read).length, ...(ngDevMode ? [{ debugName: "_unreadCount" }] : /* istanbul ignore next */ []));
11322
+ _readCount = computed(() => this.items().filter(i => i.read).length, ...(ngDevMode ? [{ debugName: "_readCount" }] : /* istanbul ignore next */ []));
11323
+ _tabs = computed(() => {
11324
+ const tabs = [
11325
+ { id: 'all', label: 'Todas', badge: this.items().length || undefined },
11326
+ { id: 'unread', label: 'Sin leer', badge: this._unreadCount() || undefined },
11327
+ ];
11328
+ const category = this.categoryTab();
11329
+ if (category)
11330
+ tabs.push({ id: category.id, label: category.label });
11331
+ return tabs;
11332
+ }, ...(ngDevMode ? [{ debugName: "_tabs" }] : /* istanbul ignore next */ []));
11333
+ _visibleItems = computed(() => {
11334
+ const f = this._filter();
11335
+ if (f === 'unread')
11336
+ return this.items().filter(i => !i.read);
11337
+ const category = this.categoryTab();
11338
+ if (category && f === category.id)
11339
+ return this.items().filter(i => i.category === category.id);
11340
+ return this.items();
11341
+ }, ...(ngDevMode ? [{ debugName: "_visibleItems" }] : /* istanbul ignore next */ []));
11342
+ _groups = computed(() => {
11343
+ const byLabel = new Map();
11344
+ for (const item of this._visibleItems()) {
11345
+ const label = this._dayLabel(item.createdAt);
11346
+ const bucket = byLabel.get(label);
11347
+ if (bucket)
11348
+ bucket.push(item);
11349
+ else
11350
+ byLabel.set(label, [item]);
11351
+ }
11352
+ return Array.from(byLabel, ([label, items]) => ({ label, items }));
11353
+ }, ...(ngDevMode ? [{ debugName: "_groups" }] : /* istanbul ignore next */ []));
11354
+ _groupLabelClass = computed(() => {
11355
+ const w = this._style().groupLabelWeight;
11356
+ return `px-2 pt-3 pb-1.5 text-[11px] uppercase tracking-wide text-clx-text-subtle font-[${w}]`;
11357
+ }, ...(ngDevMode ? [{ debugName: "_groupLabelClass" }] : /* istanbul ignore next */ []));
11358
+ _linkClass = computed(() => resolveColor(this._color()).textSubtle, ...(ngDevMode ? [{ debugName: "_linkClass" }] : /* istanbul ignore next */ []));
11359
+ _unreadRailClass = computed(() => resolveColor(this._style().unreadColor).bg, ...(ngDevMode ? [{ debugName: "_unreadRailClass" }] : /* istanbul ignore next */ []));
11360
+ _unreadDotClass = computed(() => `w-1.5 h-1.5 rounded-full shrink-0 ${resolveColor(this._style().unreadColor).bg}`, ...(ngDevMode ? [{ debugName: "_unreadDotClass" }] : /* istanbul ignore next */ []));
11361
+ _detailClass = computed(() => this._style().detailColor ? resolveColor(this._style().detailColor).textSubtle : 'text-clx-text-muted', ...(ngDevMode ? [{ debugName: "_detailClass" }] : /* istanbul ignore next */ []));
11362
+ _titleClass(item) {
11363
+ const weight = item.read ? 500 : this._style().titleWeight;
11364
+ return `text-clx-text-label font-[${weight}]`;
11365
+ }
11366
+ _iconWrapClass(item) {
11367
+ const c = resolveColor(this._severityColor(item.severity));
11368
+ return `flex items-center justify-center rounded-lg shrink-0 w-8.5 h-8.5 mt-0.5 ${c.bgSubtle} ${c.textSubtle}`;
11369
+ }
11370
+ _defaultIcon(severity) {
11371
+ return NOTIFICATION_SEVERITY_ICON[severity];
11372
+ }
11373
+ _open(item) {
11374
+ this.itemClick.emit(item);
11375
+ if (!item.read)
11376
+ this.markRead.emit(item.id);
11377
+ }
11378
+ _toggle() { this._isOpen.update(v => !v); }
11379
+ _close() { this._isOpen.set(false); }
11380
+ _onKeydown(event) {
11381
+ if (event.key === 'Escape')
11382
+ this._close();
11383
+ }
11384
+ _severityColor(severity) {
11385
+ return this._style().severityColors[severity];
11386
+ }
11387
+ _dayLabel(iso) {
11388
+ const date = new Date(iso);
11389
+ const now = new Date();
11390
+ const startOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
11391
+ const diffDays = Math.round((startOf(now) - startOf(date)) / 86_400_000);
11392
+ if (diffDays === 0)
11393
+ return 'Hoy';
11394
+ if (diffDays === 1)
11395
+ return 'Ayer';
11396
+ if (diffDays > 1 && diffDays < 7)
11397
+ return `Hace ${diffDays} días`;
11398
+ return date.toLocaleDateString('es-CO', { day: 'numeric', month: 'short' });
11399
+ }
11400
+ _timeOf(iso) {
11401
+ return new Date(iso).toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit' });
11402
+ }
11403
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxNotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11404
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxNotificationComponent, isStandalone: true, selector: "clx-notification", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, categoryTab: { classPropertyName: "categoryTab", publicName: "categoryTab", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick", markRead: "markRead", markAllRead: "markAllRead", delete: "delete", deleteAllRead: "deleteAllRead" }, host: { classAttribute: "relative inline-flex items-center" }, ngImport: i0, template: `
11405
+ <!-- Trigger -->
11406
+ <button
11407
+ #origin="cdkOverlayOrigin"
11408
+ cdkOverlayOrigin
11409
+ type="button"
11410
+ aria-label="Notificaciones"
11411
+ class="relative flex items-center justify-center w-9 h-9 rounded-full text-clx-text-muted hover:bg-clx-surface-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 cursor-pointer"
11412
+ [class]="_ringClass()"
11413
+ (click)="_toggle()">
11414
+ <span clx-icon name="notifications" size="sm"></span>
11415
+ @if (_unreadCount() > 0) {
11416
+ <span clx-badge [color]="_style().unreadColor" variant="solid" size="xs" shape="circle"
11417
+ class="absolute -top-0.5 -right-0.5">{{ _unreadCount() > 9 ? '9+' : _unreadCount() }}</span>
11418
+ }
11419
+ </button>
11420
+
11421
+ <!-- Overlay panel -->
11422
+ <ng-template
11423
+ cdkConnectedOverlay
11424
+ [cdkConnectedOverlayOrigin]="origin"
11425
+ [cdkConnectedOverlayOpen]="_isOpen()"
11426
+ [cdkConnectedOverlayPositions]="_positions"
11427
+ [cdkConnectedOverlayScrollStrategy]="_scrollStrategy"
11428
+ [cdkConnectedOverlayPush]="true"
11429
+ (overlayOutsideClick)="_close()"
11430
+ (overlayKeydown)="_onKeydown($event)">
11431
+
11432
+ <div [class]="_panelClass()">
11433
+ <!-- Header -->
11434
+ <div class="flex items-center justify-between gap-3 px-4 pt-4 pb-1">
11435
+ <p class="text-base font-semibold text-clx-text-label">Notificaciones</p>
11436
+ <div class="flex items-center gap-3">
11437
+ @if (_unreadCount() > 0) {
11438
+ <button type="button" class="text-xs font-semibold shrink-0" [class]="_linkClass()"
11439
+ (click)="markAllRead.emit()">
11440
+ Marcar todo leído
11441
+ </button>
11442
+ }
11443
+ <button type="button" class="w-6 h-6 flex items-center justify-center rounded-md text-clx-text-muted hover:bg-clx-surface-2"
11444
+ aria-label="Cerrar" (click)="_close()">
11445
+ <span clx-icon name="close" size="xs"></span>
11446
+ </button>
11447
+ </div>
11448
+ </div>
11449
+
11450
+ <!-- Tabs -->
11451
+ <div class="px-4">
11452
+ <clx-tabs [tabs]="_tabs()" [(activeTab)]="_filter" [clxColor]="_color()" />
11453
+ </div>
11454
+
11455
+ <!-- List -->
11456
+ <div class="max-h-105 overflow-y-auto px-2 pb-2">
11457
+ @if (_groups().length) {
11458
+ @for (group of _groups(); track group.label) {
11459
+ <p [class]="_groupLabelClass()">{{ group.label }}</p>
11460
+ @for (item of group.items; track item.id) {
11461
+ <div
11462
+ class="group relative flex items-start gap-3 pl-3 pr-2 rounded-lg cursor-pointer hover:bg-clx-surface-2 py-2.5"
11463
+ role="button"
11464
+ tabindex="0"
11465
+ (click)="_open(item)"
11466
+ (keydown.enter)="_open(item)">
11467
+
11468
+ <span class="absolute left-0.5 top-1 bottom-1 w-0.75 rounded-full {{ item.read ? '' : _unreadRailClass() }}"></span>
11469
+
11470
+ <span [class]="_iconWrapClass(item)">
11471
+ <span clx-icon [name]="item.icon || _defaultIcon(item.severity)" size="sm"></span>
11472
+ </span>
11473
+
11474
+ <div class="flex-1 min-w-0">
11475
+ <p class="flex items-baseline gap-1.5 text-sm" [class]="_titleClass(item)">
11476
+ @if (!item.read) {
11477
+ <span [class]="_unreadDotClass()"></span>
11478
+ }
11479
+ <span class="truncate">{{ item.title }}</span>
11480
+ </p>
11481
+ @if (item.detail) {
11482
+ <p class="mt-0.5 text-sm line-clamp-2" [class]="_detailClass()">{{ item.detail }}</p>
11483
+ }
11484
+ <p class="mt-1 text-xs text-clx-text-subtle tabular-nums">{{ _timeOf(item.createdAt) }}</p>
11485
+ </div>
11486
+
11487
+ <button type="button"
11488
+ class="opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity shrink-0 rounded-md p-1 text-clx-text-subtle hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-950"
11489
+ aria-label="Eliminar notificación"
11490
+ (click)="$event.stopPropagation(); delete.emit(item.id)">
11491
+ <span clx-icon name="close" size="xs"></span>
11492
+ </button>
11493
+ </div>
11494
+ }
11495
+ }
11496
+ } @else {
11497
+ <div class="flex flex-col items-center justify-center gap-2 py-14 text-center">
11498
+ <span clx-icon name="notifications_off" size="xl" class="text-clx-text-subtle"></span>
11499
+ <p class="text-sm font-medium text-clx-text-label">
11500
+ {{ _filter() === 'unread' ? 'No tienes notificaciones sin leer' : 'No hay notificaciones' }}
11501
+ </p>
11502
+ </div>
11503
+ }
11504
+ </div>
11505
+
11506
+ @if (_readCount() > 0) {
11507
+ <div class="flex justify-end px-4 py-2.5 border-t border-clx-border-soft">
11508
+ <button type="button" class="text-xs font-semibold text-clx-text-subtle hover:text-red-500"
11509
+ (click)="deleteAllRead.emit()">
11510
+ Eliminar leídas
11511
+ </button>
11512
+ </div>
11513
+ }
11514
+ </div>
11515
+ </ng-template>
11516
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTab"], outputs: ["activeTabChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
11517
+ }
11518
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxNotificationComponent, decorators: [{
11519
+ type: Component,
11520
+ args: [{
11521
+ selector: 'clx-notification',
11522
+ standalone: true,
11523
+ imports: [OverlayModule, ClxIconComponent, ClxBadgeComponent, ClxTabsComponent],
11524
+ template: `
11525
+ <!-- Trigger -->
11526
+ <button
11527
+ #origin="cdkOverlayOrigin"
11528
+ cdkOverlayOrigin
11529
+ type="button"
11530
+ aria-label="Notificaciones"
11531
+ class="relative flex items-center justify-center w-9 h-9 rounded-full text-clx-text-muted hover:bg-clx-surface-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 cursor-pointer"
11532
+ [class]="_ringClass()"
11533
+ (click)="_toggle()">
11534
+ <span clx-icon name="notifications" size="sm"></span>
11535
+ @if (_unreadCount() > 0) {
11536
+ <span clx-badge [color]="_style().unreadColor" variant="solid" size="xs" shape="circle"
11537
+ class="absolute -top-0.5 -right-0.5">{{ _unreadCount() > 9 ? '9+' : _unreadCount() }}</span>
11538
+ }
11539
+ </button>
11540
+
11541
+ <!-- Overlay panel -->
11542
+ <ng-template
11543
+ cdkConnectedOverlay
11544
+ [cdkConnectedOverlayOrigin]="origin"
11545
+ [cdkConnectedOverlayOpen]="_isOpen()"
11546
+ [cdkConnectedOverlayPositions]="_positions"
11547
+ [cdkConnectedOverlayScrollStrategy]="_scrollStrategy"
11548
+ [cdkConnectedOverlayPush]="true"
11549
+ (overlayOutsideClick)="_close()"
11550
+ (overlayKeydown)="_onKeydown($event)">
11551
+
11552
+ <div [class]="_panelClass()">
11553
+ <!-- Header -->
11554
+ <div class="flex items-center justify-between gap-3 px-4 pt-4 pb-1">
11555
+ <p class="text-base font-semibold text-clx-text-label">Notificaciones</p>
11556
+ <div class="flex items-center gap-3">
11557
+ @if (_unreadCount() > 0) {
11558
+ <button type="button" class="text-xs font-semibold shrink-0" [class]="_linkClass()"
11559
+ (click)="markAllRead.emit()">
11560
+ Marcar todo leído
11561
+ </button>
11562
+ }
11563
+ <button type="button" class="w-6 h-6 flex items-center justify-center rounded-md text-clx-text-muted hover:bg-clx-surface-2"
11564
+ aria-label="Cerrar" (click)="_close()">
11565
+ <span clx-icon name="close" size="xs"></span>
11566
+ </button>
11567
+ </div>
11568
+ </div>
11569
+
11570
+ <!-- Tabs -->
11571
+ <div class="px-4">
11572
+ <clx-tabs [tabs]="_tabs()" [(activeTab)]="_filter" [clxColor]="_color()" />
11573
+ </div>
11574
+
11575
+ <!-- List -->
11576
+ <div class="max-h-105 overflow-y-auto px-2 pb-2">
11577
+ @if (_groups().length) {
11578
+ @for (group of _groups(); track group.label) {
11579
+ <p [class]="_groupLabelClass()">{{ group.label }}</p>
11580
+ @for (item of group.items; track item.id) {
11581
+ <div
11582
+ class="group relative flex items-start gap-3 pl-3 pr-2 rounded-lg cursor-pointer hover:bg-clx-surface-2 py-2.5"
11583
+ role="button"
11584
+ tabindex="0"
11585
+ (click)="_open(item)"
11586
+ (keydown.enter)="_open(item)">
11587
+
11588
+ <span class="absolute left-0.5 top-1 bottom-1 w-0.75 rounded-full {{ item.read ? '' : _unreadRailClass() }}"></span>
11589
+
11590
+ <span [class]="_iconWrapClass(item)">
11591
+ <span clx-icon [name]="item.icon || _defaultIcon(item.severity)" size="sm"></span>
11592
+ </span>
11593
+
11594
+ <div class="flex-1 min-w-0">
11595
+ <p class="flex items-baseline gap-1.5 text-sm" [class]="_titleClass(item)">
11596
+ @if (!item.read) {
11597
+ <span [class]="_unreadDotClass()"></span>
11598
+ }
11599
+ <span class="truncate">{{ item.title }}</span>
11600
+ </p>
11601
+ @if (item.detail) {
11602
+ <p class="mt-0.5 text-sm line-clamp-2" [class]="_detailClass()">{{ item.detail }}</p>
11603
+ }
11604
+ <p class="mt-1 text-xs text-clx-text-subtle tabular-nums">{{ _timeOf(item.createdAt) }}</p>
11605
+ </div>
11606
+
11607
+ <button type="button"
11608
+ class="opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity shrink-0 rounded-md p-1 text-clx-text-subtle hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-950"
11609
+ aria-label="Eliminar notificación"
11610
+ (click)="$event.stopPropagation(); delete.emit(item.id)">
11611
+ <span clx-icon name="close" size="xs"></span>
11612
+ </button>
11613
+ </div>
11614
+ }
11615
+ }
11616
+ } @else {
11617
+ <div class="flex flex-col items-center justify-center gap-2 py-14 text-center">
11618
+ <span clx-icon name="notifications_off" size="xl" class="text-clx-text-subtle"></span>
11619
+ <p class="text-sm font-medium text-clx-text-label">
11620
+ {{ _filter() === 'unread' ? 'No tienes notificaciones sin leer' : 'No hay notificaciones' }}
11621
+ </p>
11622
+ </div>
11623
+ }
11624
+ </div>
11625
+
11626
+ @if (_readCount() > 0) {
11627
+ <div class="flex justify-end px-4 py-2.5 border-t border-clx-border-soft">
11628
+ <button type="button" class="text-xs font-semibold text-clx-text-subtle hover:text-red-500"
11629
+ (click)="deleteAllRead.emit()">
11630
+ Eliminar leídas
11631
+ </button>
11632
+ </div>
11633
+ }
11634
+ </div>
11635
+ </ng-template>
11636
+ `,
11637
+ encapsulation: ViewEncapsulation.None,
11638
+ changeDetection: ChangeDetectionStrategy.OnPush,
11639
+ host: { class: 'relative inline-flex items-center' },
11640
+ }]
11641
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], categoryTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "categoryTab", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], markRead: [{ type: i0.Output, args: ["markRead"] }], markAllRead: [{ type: i0.Output, args: ["markAllRead"] }], delete: [{ type: i0.Output, args: ["delete"] }], deleteAllRead: [{ type: i0.Output, args: ["deleteAllRead"] }] } });
11642
+
11239
11643
  // ─── CDK overlay token (carries the resolved ref into the component) ──────────
11240
11644
  const CLX_ALERT_OPTIONS = new InjectionToken('CLX_ALERT_OPTIONS');
11241
11645
  const CLX_ALERT_RESOLVE = new InjectionToken('CLX_ALERT_RESOLVE');
@@ -17065,5 +17469,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
17065
17469
  * Generated bundle index. Do not edit.
17066
17470
  */
17067
17471
 
17068
- export { CLX_ADDON_BORDER, CLX_ADDON_TEXT, CLX_ALERT_OPTIONS, CLX_ALERT_RESOLVE, CLX_BG_ADDON, CLX_BG_DISABLED, CLX_BG_ICON_WRAP, CLX_BG_SECTION, CLX_BG_SURFACE, CLX_BORDER_DEFAULT, CLX_BORDER_DISABLED, CLX_BORDER_MEDIUM, CLX_COLOR_HEX, CLX_COLOR_HEX_100, CLX_COLOR_MAP, CLX_FONT_CATALOG, CLX_MODAL_ANIM_CONFIG, CLX_MODAL_DATA, CLX_MODAL_REF, CLX_OPTION_DISABLED, CLX_PLACEHOLDER, CLX_RADIO_GROUP, CLX_RADIUS_MAP, CLX_TEXT_BODY, CLX_TEXT_DISABLED, CLX_TEXT_HEADING, CLX_TEXT_HINT, CLX_TEXT_IDLE, CLX_TEXT_INPUT, CLX_TEXT_LABEL, CLX_TEXT_OPTION, CLX_TEXT_SUBTITLE, CLX_TEXT_TITLE, CLX_THEME_CONFIG, CLX_THEME_DEFAULTS, CLX_TOAST_DEFAULTS, ClxAlertComponent, ClxAlertService, ClxAnimateDirective, ClxAnimateGroupDirective, ClxAnimateService, ClxAppLayoutComponent, ClxAvatarComponent, ClxBadgeComponent, ClxBrandComponent, ClxButtonComponent, ClxButtonGroupComponent, ClxCardBodyDirective, ClxCardComponent, ClxCardFooterDirective, ClxCardHeaderActionsDirective, ClxCardHeaderDirective, ClxCarouselComponent, ClxCarouselDirective, ClxCartComponent, ClxCartSummaryDrawer, ClxCellDirective, ClxCheckboxComponent, ClxCollapseComponent, ClxColorPickerComponent, ClxColumnDefDirective, ClxDateRangePickerComponent, ClxDatepickerComponent, ClxDrawerComponent, ClxDrawerService, ClxEditorComponent, ClxEditorLinkModalComponent, ClxFabComponent, ClxFilterPanelComponent, ClxHeaderCellDirective, ClxIconComponent, ClxInputComponent, ClxListComponent, ClxListItemComponent, ClxMenuComponent, ClxMenuItemComponent, ClxMenuItemTrailingDirective, ClxModalComponent, ClxModalService, ClxNativeOverlayService, ClxNavGroupComponent, ClxNumberComponent, ClxOtpComponent, ClxPageEmptyComponent, ClxPageHeaderComponent, ClxPageHeaderTitleDirective, ClxPageNotFoundComponent, ClxPageServerErrorComponent, ClxPageUnauthorizedComponent, ClxPaginationComponent, ClxProductComponent, ClxProductDetailComponent, ClxProductQuickViewComponent, ClxProfileComponent, ClxProgressBarComponent, ClxRadioComponent, ClxRadioGroupComponent, ClxRatingComponent, ClxSearchComponent, ClxSelectComponent, ClxSkeletonComponent, ClxSliderComponent, ClxSocialIconComponent, ClxSpinnerComponent, ClxStatCardComponent, ClxStepComponent, ClxStepperComponent, ClxSwitchComponent, ClxTabDirective, ClxTableActionsComponent, ClxTableComponent, ClxTabsComponent, ClxTagComponent, ClxTextareaComponent, ClxThemeService, ClxTimelineComponent, ClxTimelineItemComponent, ClxTimepickerComponent, ClxToastComponent, ClxToastContainerComponent, ClxToastService, ClxTooltipComponent, ClxTooltipDirective, ClxTreeComponent, ClxUploadComponent, ClxWishlistComponent, ClxWizardComponent, TIMEPICKER_SIZE_MAP, parseColorInput, provideCodexlyTheme, resolveColor, resolveContainerRadius, resolveRadius };
17472
+ export { CLX_ADDON_BORDER, CLX_ADDON_TEXT, CLX_ALERT_OPTIONS, CLX_ALERT_RESOLVE, CLX_BG_ADDON, CLX_BG_DISABLED, CLX_BG_ICON_WRAP, CLX_BG_SECTION, CLX_BG_SURFACE, CLX_BORDER_DEFAULT, CLX_BORDER_DISABLED, CLX_BORDER_MEDIUM, CLX_COLOR_HEX, CLX_COLOR_HEX_100, CLX_COLOR_MAP, CLX_FONT_CATALOG, CLX_MODAL_ANIM_CONFIG, CLX_MODAL_DATA, CLX_MODAL_REF, CLX_OPTION_DISABLED, CLX_PLACEHOLDER, CLX_RADIO_GROUP, CLX_RADIUS_MAP, CLX_TEXT_BODY, CLX_TEXT_DISABLED, CLX_TEXT_HEADING, CLX_TEXT_HINT, CLX_TEXT_IDLE, CLX_TEXT_INPUT, CLX_TEXT_LABEL, CLX_TEXT_OPTION, CLX_TEXT_SUBTITLE, CLX_TEXT_TITLE, CLX_THEME_CONFIG, CLX_THEME_DEFAULTS, CLX_TOAST_DEFAULTS, ClxAlertComponent, ClxAlertService, ClxAnimateDirective, ClxAnimateGroupDirective, ClxAnimateService, ClxAppLayoutComponent, ClxAvatarComponent, ClxBadgeComponent, ClxBrandComponent, ClxButtonComponent, ClxButtonGroupComponent, ClxCardBodyDirective, ClxCardComponent, ClxCardFooterDirective, ClxCardHeaderActionsDirective, ClxCardHeaderDirective, ClxCarouselComponent, ClxCarouselDirective, ClxCartComponent, ClxCartSummaryDrawer, ClxCellDirective, ClxCheckboxComponent, ClxCollapseComponent, ClxColorPickerComponent, ClxColumnDefDirective, ClxDateRangePickerComponent, ClxDatepickerComponent, ClxDrawerComponent, ClxDrawerService, ClxEditorComponent, ClxEditorLinkModalComponent, ClxFabComponent, ClxFilterPanelComponent, ClxHeaderCellDirective, ClxIconComponent, ClxInputComponent, ClxListComponent, ClxListItemComponent, ClxMenuComponent, ClxMenuItemComponent, ClxMenuItemTrailingDirective, ClxModalComponent, ClxModalService, ClxNativeOverlayService, ClxNavGroupComponent, ClxNotificationComponent, ClxNumberComponent, ClxOtpComponent, ClxPageEmptyComponent, ClxPageHeaderComponent, ClxPageHeaderTitleDirective, ClxPageNotFoundComponent, ClxPageServerErrorComponent, ClxPageUnauthorizedComponent, ClxPaginationComponent, ClxProductComponent, ClxProductDetailComponent, ClxProductQuickViewComponent, ClxProfileComponent, ClxProgressBarComponent, ClxRadioComponent, ClxRadioGroupComponent, ClxRatingComponent, ClxSearchComponent, ClxSelectComponent, ClxSkeletonComponent, ClxSliderComponent, ClxSocialIconComponent, ClxSpinnerComponent, ClxStatCardComponent, ClxStepComponent, ClxStepperComponent, ClxSwitchComponent, ClxTabDirective, ClxTableActionsComponent, ClxTableComponent, ClxTabsComponent, ClxTagComponent, ClxTextareaComponent, ClxThemeService, ClxTimelineComponent, ClxTimelineItemComponent, ClxTimepickerComponent, ClxToastComponent, ClxToastContainerComponent, ClxToastService, ClxTooltipComponent, ClxTooltipDirective, ClxTreeComponent, ClxUploadComponent, ClxWishlistComponent, ClxWizardComponent, TIMEPICKER_SIZE_MAP, parseColorInput, provideCodexlyTheme, resolveColor, resolveContainerRadius, resolveRadius };
17069
17473
  //# sourceMappingURL=codexly-ui.mjs.map