@excellore/ui 0.0.2 → 0.0.4

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.
@@ -2,8 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { inject, PLATFORM_ID, signal, effect, Injectable, input, output, HostListener, Component, computed } from '@angular/core';
3
3
  import { isPlatformBrowser, CommonModule } from '@angular/common';
4
4
  import { Router, RouterLink, RouterLinkActive } from '@angular/router';
5
- import * as i1 from 'lucide-angular';
6
- import { Grid, ChevronDown, LucideAngularModule, Check, Building, CheckCheck, Trash2, BellOff, Circle, Bell, User, Settings, CreditCard, LogOut, Building2, Monitor, Moon, Sun, X, Menu, Search } from 'lucide-angular';
5
+ import { LucideGrid, LucideBuilding, LucideCheck, LucideChevronDown, LucideBellOff, LucideCheckCheck, LucideTrash2, LucideBell, LucideCreditCard, LucideLogOut, LucideSettings, LucideUser, LucideBuilding2, LucideMenu, LucideMonitor, LucideMoon, LucideSearch, LucideSun, LucideX } from '@lucide/angular';
7
6
 
8
7
  const THEME_STORAGE_KEY = 'excellore-theme';
9
8
  class ThemeService {
@@ -123,8 +122,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
123
122
  class AppSwitcherComponent {
124
123
  applications = input([], ...(ngDevMode ? [{ debugName: "applications" }] : /* istanbul ignore next */ []));
125
124
  applicationSelected = output();
126
- gridIcon = Grid;
127
- chevronDownIcon = ChevronDown;
128
125
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
129
126
  toggleSwitcher() {
130
127
  this.isOpen.update((v) => !v);
@@ -153,11 +150,11 @@ class AppSwitcherComponent {
153
150
  }
154
151
  }
155
152
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AppSwitcherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
156
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: AppSwitcherComponent, isStandalone: true, selector: "lib-app-switcher", inputs: { applications: { classPropertyName: "applications", publicName: "applications", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { applicationSelected: "applicationSelected" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, ngImport: i0, template: "<div class=\"app-switcher-wrapper relative inline-block text-left\">\n <button\n type=\"button\"\n class=\"grid h-9 w-9 place-items-center rounded-md border border-transparent text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"toggleSwitcher()\"\n aria-haspopup=\"true\"\n [aria-expanded]=\"isOpen()\"\n aria-label=\"Toggle application switcher\"\n >\n <lucide-icon [name]=\"gridIcon\" class=\"h-4.5 w-4.5\" />\n </button>\n\n @if (isOpen()) {\n <div class=\"app-switcher-dropdown absolute right-0 mt-2 w-[320px] origin-top-right rounded-lg border border-[var(--border)] bg-[var(--background)] p-3 shadow-lg focus:outline-none z-50\">\n <div class=\"mb-2.5 flex items-center justify-between border-b border-[var(--border)] pb-2\">\n <h3 class=\"text-[10px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Excellore Apps</h3>\n </div>\n \n @if (applications().length === 0) {\n <div class=\"py-6 text-center text-xs text-[var(--secondary)]\">\n No other apps available\n </div>\n } @else {\n <div class=\"grid grid-cols-3 gap-1.5\">\n @for (app of applications(); track app.id) {\n @if (app.available !== false) {\n <a\n [href]=\"app.url\"\n (click)=\"onSelect(app)\"\n class=\"flex flex-col items-center gap-1.5 rounded-lg p-2 text-center no-underline transition hover:bg-[rgba(var(--foreground),0.05)] border border-transparent hover:border-[var(--border)] cursor-pointer\"\n >\n <div class=\"flex h-10 w-10 items-center justify-center rounded-lg bg-[rgba(var(--primary),0.08)] text-[var(--primary)] text-sm font-bold border border-[rgba(var(--primary),0.12)]\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <span class=\"text-xs font-medium text-[var(--foreground)] truncate w-full\" [title]=\"app.name\">{{ app.name }}</span>\n </a>\n } @else {\n <div class=\"flex flex-col items-center gap-1.5 rounded-lg p-2 text-center opacity-40 select-none\">\n <div class=\"flex h-10 w-10 items-center justify-center rounded-lg bg-[rgba(var(--foreground),0.04)] text-[var(--secondary)] text-sm font-bold border border-dashed border-[var(--border)]\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"flex flex-col items-center w-full\">\n <span class=\"text-xs font-medium text-[var(--foreground)] truncate w-full\">{{ app.name }}</span>\n <span class=\"text-[9px] font-medium tracking-wider text-[var(--secondary)] uppercase\">Soon</span>\n </div>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{display:inline-block}.app-switcher-dropdown{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:switcherEnter .15s cubic-bezier(.16,1,.3,1)}@keyframes switcherEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
153
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: AppSwitcherComponent, isStandalone: true, selector: "lib-app-switcher", inputs: { applications: { classPropertyName: "applications", publicName: "applications", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { applicationSelected: "applicationSelected" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, ngImport: i0, template: "<div class=\"app-switcher-wrapper relative inline-block text-left\">\r\n <button\n type=\"button\"\n class=\"app-switcher-trigger\"\n (click)=\"toggleSwitcher()\"\r\n aria-haspopup=\"true\"\r\n [aria-expanded]=\"isOpen()\"\r\n aria-label=\"Toggle application switcher\"\r\n >\r\n <svg lucideGrid class=\"h-4.5 w-4.5\"></svg>\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"app-switcher-dropdown fixed left-2 right-2 top-14 mt-2 max-h-[calc(100dvh-4.5rem)] origin-top rounded-xl border border-[var(--border)] bg-[var(--background)] p-2.5 shadow-lg focus:outline-none z-50 sm:absolute sm:left-auto sm:right-0 sm:top-auto sm:w-[360px] sm:max-w-[calc(100vw-1rem)] sm:origin-top-right\">\n <div class=\"mb-1.5 flex items-center justify-between border-b border-[var(--border)] px-1 pb-2.5\">\n <div>\n <h3 class=\"text-[10px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Excellore Apps</h3>\n <p class=\"mt-0.5 text-[11px] text-[var(--secondary)]\">Switch between available workspaces</p>\n </div>\n </div>\n \n @if (applications().length === 0) {\n <div class=\"py-6 text-center text-xs text-[var(--secondary)]\">\n No other apps available\n </div>\n } @else {\n <div class=\"max-h-[calc(100dvh-10rem)] overflow-y-auto py-1 sm:max-h-[420px]\">\n @for (app of applications(); track app.id) {\n @if (app.available !== false) {\n <a\n [href]=\"app.url\"\n (click)=\"onSelect(app)\"\n class=\"app-switcher-item app-switcher-link group flex items-center gap-3 rounded-lg border border-transparent px-2.5 py-2.5 no-underline transition focus:outline-none focus:ring-2 focus:ring-[var(--primary)]\"\n >\n <div class=\"app-switcher-app-mark\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"min-w-0 flex-1\">\n <div class=\"truncate text-sm font-semibold text-[var(--foreground)]\" [title]=\"app.name\">{{ app.name }}</div>\n <div class=\"mt-0.5 truncate text-[11px] text-[var(--secondary)]\">{{ app.url }}</div>\n </div>\n <span class=\"app-switcher-open-pill shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide opacity-0 transition group-hover:opacity-100\">\n Open\n </span>\n </a>\n } @else {\n <div class=\"app-switcher-item flex items-center gap-3 rounded-lg px-2.5 py-2.5 opacity-55 select-none\">\n <div class=\"app-switcher-app-mark unavailable\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"min-w-0 flex-1\">\n <div class=\"truncate text-sm font-semibold text-[var(--foreground)]\">{{ app.name }}</div>\n <div class=\"mt-0.5 truncate text-[11px] text-[var(--secondary)]\">{{ app.url }}</div>\n </div>\n <span class=\"shrink-0 rounded-full border border-[var(--border)] px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-[var(--secondary)]\">\n Soon\n </span>\n </div>\n }\n }\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.app-switcher-trigger{display:grid;width:2.15rem;height:2.15rem;place-items:center;border:1px solid transparent;border-radius:.5rem;color:var(--foreground);background:transparent;cursor:pointer;transition:background-color .15s ease,border-color .15s ease}.app-switcher-trigger:hover{border-color:var(--border);background:color-mix(in srgb,var(--border) 35%,transparent)}.app-switcher-trigger:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.app-switcher-dropdown{box-shadow:0 12px 30px #0f172a24;animation:switcherEnter .15s cubic-bezier(.16,1,.3,1)}.app-switcher-item+.app-switcher-item{margin-top:.125rem}.app-switcher-link:hover{border-color:var(--border);background:color-mix(in srgb,var(--border) 34%,transparent)}.app-switcher-app-mark{display:flex;width:2.5rem;height:2.5rem;align-items:center;justify-content:center;flex-shrink:0;border-radius:.7rem;color:var(--primary);background:color-mix(in srgb,var(--primary) 9%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--primary) 15%,transparent);font-size:.875rem;font-weight:800}.app-switcher-app-mark.unavailable{border:1px dashed var(--border);color:var(--secondary);background:color-mix(in srgb,var(--border) 32%,transparent);box-shadow:none}.app-switcher-open-pill{color:var(--primary);background:color-mix(in srgb,var(--primary) 9%,transparent)}.app-switcher-dropdown ::-webkit-scrollbar{width:.45rem}.app-switcher-dropdown ::-webkit-scrollbar-track{background:transparent}.app-switcher-dropdown ::-webkit-scrollbar-thumb{background:var(--border);border-radius:999px}@keyframes switcherEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideGrid, selector: "svg[lucideGrid3x3], svg[lucideGrid], svg[lucideGrid3X3]" }] });
157
154
  }
158
155
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AppSwitcherComponent, decorators: [{
159
156
  type: Component,
160
- args: [{ selector: 'lib-app-switcher', standalone: true, imports: [CommonModule, LucideAngularModule], template: "<div class=\"app-switcher-wrapper relative inline-block text-left\">\n <button\n type=\"button\"\n class=\"grid h-9 w-9 place-items-center rounded-md border border-transparent text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"toggleSwitcher()\"\n aria-haspopup=\"true\"\n [aria-expanded]=\"isOpen()\"\n aria-label=\"Toggle application switcher\"\n >\n <lucide-icon [name]=\"gridIcon\" class=\"h-4.5 w-4.5\" />\n </button>\n\n @if (isOpen()) {\n <div class=\"app-switcher-dropdown absolute right-0 mt-2 w-[320px] origin-top-right rounded-lg border border-[var(--border)] bg-[var(--background)] p-3 shadow-lg focus:outline-none z-50\">\n <div class=\"mb-2.5 flex items-center justify-between border-b border-[var(--border)] pb-2\">\n <h3 class=\"text-[10px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Excellore Apps</h3>\n </div>\n \n @if (applications().length === 0) {\n <div class=\"py-6 text-center text-xs text-[var(--secondary)]\">\n No other apps available\n </div>\n } @else {\n <div class=\"grid grid-cols-3 gap-1.5\">\n @for (app of applications(); track app.id) {\n @if (app.available !== false) {\n <a\n [href]=\"app.url\"\n (click)=\"onSelect(app)\"\n class=\"flex flex-col items-center gap-1.5 rounded-lg p-2 text-center no-underline transition hover:bg-[rgba(var(--foreground),0.05)] border border-transparent hover:border-[var(--border)] cursor-pointer\"\n >\n <div class=\"flex h-10 w-10 items-center justify-center rounded-lg bg-[rgba(var(--primary),0.08)] text-[var(--primary)] text-sm font-bold border border-[rgba(var(--primary),0.12)]\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <span class=\"text-xs font-medium text-[var(--foreground)] truncate w-full\" [title]=\"app.name\">{{ app.name }}</span>\n </a>\n } @else {\n <div class=\"flex flex-col items-center gap-1.5 rounded-lg p-2 text-center opacity-40 select-none\">\n <div class=\"flex h-10 w-10 items-center justify-center rounded-lg bg-[rgba(var(--foreground),0.04)] text-[var(--secondary)] text-sm font-bold border border-dashed border-[var(--border)]\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"flex flex-col items-center w-full\">\n <span class=\"text-xs font-medium text-[var(--foreground)] truncate w-full\">{{ app.name }}</span>\n <span class=\"text-[9px] font-medium tracking-wider text-[var(--secondary)] uppercase\">Soon</span>\n </div>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{display:inline-block}.app-switcher-dropdown{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:switcherEnter .15s cubic-bezier(.16,1,.3,1)}@keyframes switcherEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
157
+ args: [{ selector: 'lib-app-switcher', standalone: true, imports: [CommonModule, LucideGrid], template: "<div class=\"app-switcher-wrapper relative inline-block text-left\">\r\n <button\n type=\"button\"\n class=\"app-switcher-trigger\"\n (click)=\"toggleSwitcher()\"\r\n aria-haspopup=\"true\"\r\n [aria-expanded]=\"isOpen()\"\r\n aria-label=\"Toggle application switcher\"\r\n >\r\n <svg lucideGrid class=\"h-4.5 w-4.5\"></svg>\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"app-switcher-dropdown fixed left-2 right-2 top-14 mt-2 max-h-[calc(100dvh-4.5rem)] origin-top rounded-xl border border-[var(--border)] bg-[var(--background)] p-2.5 shadow-lg focus:outline-none z-50 sm:absolute sm:left-auto sm:right-0 sm:top-auto sm:w-[360px] sm:max-w-[calc(100vw-1rem)] sm:origin-top-right\">\n <div class=\"mb-1.5 flex items-center justify-between border-b border-[var(--border)] px-1 pb-2.5\">\n <div>\n <h3 class=\"text-[10px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Excellore Apps</h3>\n <p class=\"mt-0.5 text-[11px] text-[var(--secondary)]\">Switch between available workspaces</p>\n </div>\n </div>\n \n @if (applications().length === 0) {\n <div class=\"py-6 text-center text-xs text-[var(--secondary)]\">\n No other apps available\n </div>\n } @else {\n <div class=\"max-h-[calc(100dvh-10rem)] overflow-y-auto py-1 sm:max-h-[420px]\">\n @for (app of applications(); track app.id) {\n @if (app.available !== false) {\n <a\n [href]=\"app.url\"\n (click)=\"onSelect(app)\"\n class=\"app-switcher-item app-switcher-link group flex items-center gap-3 rounded-lg border border-transparent px-2.5 py-2.5 no-underline transition focus:outline-none focus:ring-2 focus:ring-[var(--primary)]\"\n >\n <div class=\"app-switcher-app-mark\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"min-w-0 flex-1\">\n <div class=\"truncate text-sm font-semibold text-[var(--foreground)]\" [title]=\"app.name\">{{ app.name }}</div>\n <div class=\"mt-0.5 truncate text-[11px] text-[var(--secondary)]\">{{ app.url }}</div>\n </div>\n <span class=\"app-switcher-open-pill shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide opacity-0 transition group-hover:opacity-100\">\n Open\n </span>\n </a>\n } @else {\n <div class=\"app-switcher-item flex items-center gap-3 rounded-lg px-2.5 py-2.5 opacity-55 select-none\">\n <div class=\"app-switcher-app-mark unavailable\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"min-w-0 flex-1\">\n <div class=\"truncate text-sm font-semibold text-[var(--foreground)]\">{{ app.name }}</div>\n <div class=\"mt-0.5 truncate text-[11px] text-[var(--secondary)]\">{{ app.url }}</div>\n </div>\n <span class=\"shrink-0 rounded-full border border-[var(--border)] px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-[var(--secondary)]\">\n Soon\n </span>\n </div>\n }\n }\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.app-switcher-trigger{display:grid;width:2.15rem;height:2.15rem;place-items:center;border:1px solid transparent;border-radius:.5rem;color:var(--foreground);background:transparent;cursor:pointer;transition:background-color .15s ease,border-color .15s ease}.app-switcher-trigger:hover{border-color:var(--border);background:color-mix(in srgb,var(--border) 35%,transparent)}.app-switcher-trigger:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.app-switcher-dropdown{box-shadow:0 12px 30px #0f172a24;animation:switcherEnter .15s cubic-bezier(.16,1,.3,1)}.app-switcher-item+.app-switcher-item{margin-top:.125rem}.app-switcher-link:hover{border-color:var(--border);background:color-mix(in srgb,var(--border) 34%,transparent)}.app-switcher-app-mark{display:flex;width:2.5rem;height:2.5rem;align-items:center;justify-content:center;flex-shrink:0;border-radius:.7rem;color:var(--primary);background:color-mix(in srgb,var(--primary) 9%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--primary) 15%,transparent);font-size:.875rem;font-weight:800}.app-switcher-app-mark.unavailable{border:1px dashed var(--border);color:var(--secondary);background:color-mix(in srgb,var(--border) 32%,transparent);box-shadow:none}.app-switcher-open-pill{color:var(--primary);background:color-mix(in srgb,var(--primary) 9%,transparent)}.app-switcher-dropdown ::-webkit-scrollbar{width:.45rem}.app-switcher-dropdown ::-webkit-scrollbar-track{background:transparent}.app-switcher-dropdown ::-webkit-scrollbar-thumb{background:var(--border);border-radius:999px}@keyframes switcherEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
161
158
  }], propDecorators: { applications: [{ type: i0.Input, args: [{ isSignal: true, alias: "applications", required: false }] }], applicationSelected: [{ type: i0.Output, args: ["applicationSelected"] }], onOutsideClick: [{
162
159
  type: HostListener,
163
160
  args: ['document:click', ['$event']]
@@ -167,9 +164,6 @@ class OrganizationSwitcherComponent {
167
164
  organizations = input([], ...(ngDevMode ? [{ debugName: "organizations" }] : /* istanbul ignore next */ []));
168
165
  selectedOrganization = input(null, ...(ngDevMode ? [{ debugName: "selectedOrganization" }] : /* istanbul ignore next */ []));
169
166
  organizationChanged = output();
170
- chevronDownIcon = ChevronDown;
171
- checkIcon = Check;
172
- buildingIcon = Building;
173
167
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
174
168
  toggleSwitcher() {
175
169
  this.isOpen.update((v) => !v);
@@ -188,11 +182,11 @@ class OrganizationSwitcherComponent {
188
182
  }
189
183
  }
190
184
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: OrganizationSwitcherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
191
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: OrganizationSwitcherComponent, isStandalone: true, selector: "lib-organization-switcher", inputs: { organizations: { classPropertyName: "organizations", publicName: "organizations", isSignal: true, isRequired: false, transformFunction: null }, selectedOrganization: { classPropertyName: "selectedOrganization", publicName: "selectedOrganization", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { organizationChanged: "organizationChanged" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, ngImport: i0, template: "<div class=\"org-switcher-wrapper relative inline-block text-left\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1.5 rounded-md border border-[var(--border)] bg-[var(--background)] px-2.5 py-1.5 text-xs font-medium text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"toggleSwitcher()\"\n aria-haspopup=\"true\"\n [aria-expanded]=\"isOpen()\"\n aria-label=\"Switch organization\"\n >\n <lucide-icon [name]=\"buildingIcon\" class=\"h-3.5 w-3.5 text-[var(--secondary)]\" />\n <span class=\"truncate max-w-[120px]\">{{ selectedOrganization()?.name || 'Select Tenant' }}</span>\n <lucide-icon [name]=\"chevronDownIcon\" class=\"h-3 w-3 text-[var(--secondary)] transition-transform duration-200\" [class.rotate-180]=\"isOpen()\" />\n </button>\n\n @if (isOpen()) {\n <div class=\"org-switcher-dropdown absolute left-0 mt-2 w-52 origin-top-left rounded-lg border border-[var(--border)] bg-[var(--background)] p-1 shadow-lg focus:outline-none z-50\">\n <div class=\"px-2 py-1.5 border-b border-[var(--border)] mb-1\">\n <span class=\"text-[9px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Switch Organization</span>\n </div>\n\n <div class=\"py-0.5 space-y-0.5 max-h-48 overflow-y-auto\">\n @for (org of organizations(); track org.id) {\n <button\n type=\"button\"\n class=\"flex w-full items-center justify-between rounded-md px-2 py-1.5 text-xs text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\n (click)=\"onSelect(org)\"\n >\n <span class=\"truncate pr-2\" [class.font-semibold]=\"org.id === selectedOrganization()?.id\">{{ org.name }}</span>\n @if (org.id === selectedOrganization()?.id) {\n <lucide-icon [name]=\"checkIcon\" class=\"h-3.5 w-3.5 text-[var(--primary)] flex-shrink-0\" />\n }\n </button>\n }\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:inline-block}.org-switcher-dropdown{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:orgEnter .15s cubic-bezier(.16,1,.3,1)}@keyframes orgEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
185
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: OrganizationSwitcherComponent, isStandalone: true, selector: "lib-organization-switcher", inputs: { organizations: { classPropertyName: "organizations", publicName: "organizations", isSignal: true, isRequired: false, transformFunction: null }, selectedOrganization: { classPropertyName: "selectedOrganization", publicName: "selectedOrganization", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { organizationChanged: "organizationChanged" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, ngImport: i0, template: "<div class=\"org-switcher-wrapper relative inline-block text-left\">\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center gap-1.5 rounded-md border border-[var(--border)] bg-[var(--background)] px-2.5 py-1.5 text-xs font-medium text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\r\n (click)=\"toggleSwitcher()\"\r\n aria-haspopup=\"true\"\r\n [aria-expanded]=\"isOpen()\"\r\n aria-label=\"Switch organization\"\r\n >\r\n <svg lucideBuilding class=\"h-3.5 w-3.5 text-[var(--secondary)]\"></svg>\n <span class=\"truncate max-w-[120px]\">{{ selectedOrganization()?.name || 'Select Tenant' }}</span>\n <svg lucideChevronDown class=\"h-3 w-3 text-[var(--secondary)] transition-transform duration-200\" [class.rotate-180]=\"isOpen()\"></svg>\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"org-switcher-dropdown absolute left-0 mt-2 w-52 origin-top-left rounded-lg border border-[var(--border)] bg-[var(--background)] p-1 shadow-lg focus:outline-none z-50\">\r\n <div class=\"px-2 py-1.5 border-b border-[var(--border)] mb-1\">\r\n <span class=\"text-[9px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Switch Organization</span>\r\n </div>\r\n\r\n <div class=\"py-0.5 space-y-0.5 max-h-48 overflow-y-auto\">\r\n @for (org of organizations(); track org.id) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full items-center justify-between rounded-md px-2 py-1.5 text-xs text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\r\n (click)=\"onSelect(org)\"\r\n >\r\n <span class=\"truncate pr-2\" [class.font-semibold]=\"org.id === selectedOrganization()?.id\">{{ org.name }}</span>\r\n @if (org.id === selectedOrganization()?.id) {\r\n <svg lucideCheck class=\"h-3.5 w-3.5 text-[var(--primary)] flex-shrink-0\"></svg>\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.org-switcher-dropdown{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:orgEnter .15s cubic-bezier(.16,1,.3,1)}@keyframes orgEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideBuilding, selector: "svg[lucideBuilding]" }, { kind: "component", type: LucideCheck, selector: "svg[lucideCheck]" }, { kind: "component", type: LucideChevronDown, selector: "svg[lucideChevronDown]" }] });
192
186
  }
193
187
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: OrganizationSwitcherComponent, decorators: [{
194
188
  type: Component,
195
- args: [{ selector: 'lib-organization-switcher', standalone: true, imports: [CommonModule, LucideAngularModule], template: "<div class=\"org-switcher-wrapper relative inline-block text-left\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1.5 rounded-md border border-[var(--border)] bg-[var(--background)] px-2.5 py-1.5 text-xs font-medium text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"toggleSwitcher()\"\n aria-haspopup=\"true\"\n [aria-expanded]=\"isOpen()\"\n aria-label=\"Switch organization\"\n >\n <lucide-icon [name]=\"buildingIcon\" class=\"h-3.5 w-3.5 text-[var(--secondary)]\" />\n <span class=\"truncate max-w-[120px]\">{{ selectedOrganization()?.name || 'Select Tenant' }}</span>\n <lucide-icon [name]=\"chevronDownIcon\" class=\"h-3 w-3 text-[var(--secondary)] transition-transform duration-200\" [class.rotate-180]=\"isOpen()\" />\n </button>\n\n @if (isOpen()) {\n <div class=\"org-switcher-dropdown absolute left-0 mt-2 w-52 origin-top-left rounded-lg border border-[var(--border)] bg-[var(--background)] p-1 shadow-lg focus:outline-none z-50\">\n <div class=\"px-2 py-1.5 border-b border-[var(--border)] mb-1\">\n <span class=\"text-[9px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Switch Organization</span>\n </div>\n\n <div class=\"py-0.5 space-y-0.5 max-h-48 overflow-y-auto\">\n @for (org of organizations(); track org.id) {\n <button\n type=\"button\"\n class=\"flex w-full items-center justify-between rounded-md px-2 py-1.5 text-xs text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\n (click)=\"onSelect(org)\"\n >\n <span class=\"truncate pr-2\" [class.font-semibold]=\"org.id === selectedOrganization()?.id\">{{ org.name }}</span>\n @if (org.id === selectedOrganization()?.id) {\n <lucide-icon [name]=\"checkIcon\" class=\"h-3.5 w-3.5 text-[var(--primary)] flex-shrink-0\" />\n }\n </button>\n }\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:inline-block}.org-switcher-dropdown{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:orgEnter .15s cubic-bezier(.16,1,.3,1)}@keyframes orgEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
189
+ args: [{ selector: 'lib-organization-switcher', standalone: true, imports: [CommonModule, LucideBuilding, LucideCheck, LucideChevronDown], template: "<div class=\"org-switcher-wrapper relative inline-block text-left\">\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center gap-1.5 rounded-md border border-[var(--border)] bg-[var(--background)] px-2.5 py-1.5 text-xs font-medium text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\r\n (click)=\"toggleSwitcher()\"\r\n aria-haspopup=\"true\"\r\n [aria-expanded]=\"isOpen()\"\r\n aria-label=\"Switch organization\"\r\n >\r\n <svg lucideBuilding class=\"h-3.5 w-3.5 text-[var(--secondary)]\"></svg>\n <span class=\"truncate max-w-[120px]\">{{ selectedOrganization()?.name || 'Select Tenant' }}</span>\n <svg lucideChevronDown class=\"h-3 w-3 text-[var(--secondary)] transition-transform duration-200\" [class.rotate-180]=\"isOpen()\"></svg>\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"org-switcher-dropdown absolute left-0 mt-2 w-52 origin-top-left rounded-lg border border-[var(--border)] bg-[var(--background)] p-1 shadow-lg focus:outline-none z-50\">\r\n <div class=\"px-2 py-1.5 border-b border-[var(--border)] mb-1\">\r\n <span class=\"text-[9px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Switch Organization</span>\r\n </div>\r\n\r\n <div class=\"py-0.5 space-y-0.5 max-h-48 overflow-y-auto\">\r\n @for (org of organizations(); track org.id) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full items-center justify-between rounded-md px-2 py-1.5 text-xs text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\r\n (click)=\"onSelect(org)\"\r\n >\r\n <span class=\"truncate pr-2\" [class.font-semibold]=\"org.id === selectedOrganization()?.id\">{{ org.name }}</span>\r\n @if (org.id === selectedOrganization()?.id) {\r\n <svg lucideCheck class=\"h-3.5 w-3.5 text-[var(--primary)] flex-shrink-0\"></svg>\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.org-switcher-dropdown{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:orgEnter .15s cubic-bezier(.16,1,.3,1)}@keyframes orgEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
196
190
  }], propDecorators: { organizations: [{ type: i0.Input, args: [{ isSignal: true, alias: "organizations", required: false }] }], selectedOrganization: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedOrganization", required: false }] }], organizationChanged: [{ type: i0.Output, args: ["organizationChanged"] }], onOutsideClick: [{
197
191
  type: HostListener,
198
192
  args: ['document:click', ['$event']]
@@ -203,10 +197,6 @@ class NotificationPanelComponent {
203
197
  notificationClick = output();
204
198
  markRead = output();
205
199
  clearAll = output();
206
- checkCheckIcon = CheckCheck;
207
- trashIcon = Trash2;
208
- bellOffIcon = BellOff;
209
- circleIcon = Circle;
210
200
  onNotificationClick(notification) {
211
201
  this.notificationClick.emit(notification);
212
202
  }
@@ -236,11 +226,11 @@ class NotificationPanelComponent {
236
226
  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
237
227
  }
238
228
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NotificationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
239
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: NotificationPanelComponent, isStandalone: true, selector: "lib-notification-panel", inputs: { notifications: { classPropertyName: "notifications", publicName: "notifications", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { notificationClick: "notificationClick", markRead: "markRead", clearAll: "clearAll" }, ngImport: i0, template: "<div class=\"notification-panel-inner flex flex-col h-full max-h-[380px] w-full text-left\">\n <!-- Panel Header -->\n <div class=\"flex items-center justify-between border-b border-[var(--border)] px-3.5 py-2\">\n <span class=\"text-[10px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Notifications</span>\n @if (notifications().length > 0) {\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-[9px] font-bold text-[var(--secondary)] hover:text-[var(--danger,#ef4444)] hover:underline cursor-pointer\"\n (click)=\"onClearAll($event)\"\n >\n <lucide-icon [name]=\"trashIcon\" class=\"h-3 w-3\" />\n <span>Clear All</span>\n </button>\n }\n </div>\n\n <!-- Panel Body -->\n <div class=\"flex-1 overflow-y-auto min-h-0 py-0.5 divide-y divide-[var(--border)]\">\n @if (notifications().length === 0) {\n <div class=\"flex flex-col items-center justify-center py-10 px-4 text-center select-none\">\n <div class=\"flex h-10 w-10 items-center justify-center rounded-full bg-[rgba(var(--secondary),0.06)] text-[var(--secondary)] mb-2.5\">\n <lucide-icon [name]=\"bellOffIcon\" class=\"h-4.5 w-4.5\" />\n </div>\n <p class=\"text-xs font-semibold text-[var(--foreground)]\">All caught up</p>\n <p class=\"text-[10px] text-[var(--secondary)] mt-0.5\">No new notifications to display.</p>\n </div>\n } @else {\n @for (item of notifications(); track item.id) {\n <div\n (click)=\"onNotificationClick(item)\"\n class=\"notification-item flex gap-2.5 p-3 transition hover:bg-[rgba(var(--foreground),0.02)] cursor-pointer relative\"\n [class.bg-[rgba(var(--primary),0.015)]]=\"!item.read\"\n >\n <!-- Read status dot -->\n <div class=\"flex-shrink-0 pt-1\">\n @if (!item.read) {\n <span class=\"block h-1.5 w-1.5 rounded-full bg-[var(--primary)] animate-pulse\"></span>\n } @else {\n <span class=\"block h-1.5 w-1.5 rounded-full bg-transparent\"></span>\n }\n </div>\n\n <!-- Notification content -->\n <div class=\"flex-1 min-w-0\">\n <h4 class=\"text-xs font-semibold text-[var(--foreground)] truncate\" [class.font-bold]=\"!item.read\">\n {{ item.title }}\n </h4>\n <p class=\"text-[10.5px] text-[var(--secondary)] mt-0.5 leading-snug break-words\">\n {{ item.message }}\n </p>\n <span class=\"block text-[9px] text-[var(--secondary)] mt-1 font-medium\">\n {{ formatTime(item.createdAt) }}\n </span>\n </div>\n\n <!-- Actions -->\n @if (!item.read) {\n <div class=\"flex items-center flex-shrink-0\">\n <button\n type=\"button\"\n class=\"mark-read-btn grid h-6 w-6 place-items-center rounded-full text-[var(--secondary)] hover:text-[var(--primary)] hover:bg-[rgba(var(--foreground),0.05)] cursor-pointer\"\n [title]=\"'Mark as read'\"\n (click)=\"onMarkRead(item.id, $event)\"\n >\n <lucide-icon [name]=\"checkCheckIcon\" class=\"h-3.5 w-3.5\" />\n </button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%}.notification-item{border-left:2px solid transparent}.notification-item:hover{border-left-color:var(--primary)}.mark-read-btn{opacity:.5;transition:opacity .2s,background-color .2s}.notification-item:hover .mark-read-btn{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
229
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: NotificationPanelComponent, isStandalone: true, selector: "lib-notification-panel", inputs: { notifications: { classPropertyName: "notifications", publicName: "notifications", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { notificationClick: "notificationClick", markRead: "markRead", clearAll: "clearAll" }, ngImport: i0, template: "<div class=\"notification-panel-inner flex flex-col h-full max-h-[380px] w-full text-left\">\r\n <!-- Panel Header -->\r\n <div class=\"flex items-center justify-between border-b border-[var(--border)] px-3.5 py-2\">\r\n <span class=\"text-[10px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Notifications</span>\r\n @if (notifications().length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center gap-1 text-[9px] font-bold text-[var(--secondary)] hover:text-[var(--danger,#ef4444)] hover:underline cursor-pointer\"\r\n (click)=\"onClearAll($event)\"\r\n >\r\n <svg lucideTrash2 class=\"h-3 w-3\"></svg>\n <span>Clear All</span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Panel Body -->\r\n <div class=\"flex-1 overflow-y-auto min-h-0 py-0.5 divide-y divide-[var(--border)]\">\r\n @if (notifications().length === 0) {\r\n <div class=\"flex flex-col items-center justify-center py-10 px-4 text-center select-none\">\r\n <div class=\"flex h-10 w-10 items-center justify-center rounded-full bg-[rgba(var(--secondary),0.06)] text-[var(--secondary)] mb-2.5\">\r\n <svg lucideBellOff class=\"h-4.5 w-4.5\"></svg>\n </div>\r\n <p class=\"text-xs font-semibold text-[var(--foreground)]\">All caught up</p>\r\n <p class=\"text-[10px] text-[var(--secondary)] mt-0.5\">No new notifications to display.</p>\r\n </div>\r\n } @else {\r\n @for (item of notifications(); track item.id) {\r\n <div\r\n (click)=\"onNotificationClick(item)\"\r\n class=\"notification-item flex gap-2.5 p-3 transition hover:bg-[rgba(var(--foreground),0.02)] cursor-pointer relative\"\r\n [class.bg-[rgba(var(--primary),0.015)]]=\"!item.read\"\r\n >\r\n <!-- Read status dot -->\r\n <div class=\"flex-shrink-0 pt-1\">\r\n @if (!item.read) {\r\n <span class=\"block h-1.5 w-1.5 rounded-full bg-[var(--primary)] animate-pulse\"></span>\r\n } @else {\r\n <span class=\"block h-1.5 w-1.5 rounded-full bg-transparent\"></span>\r\n }\r\n </div>\r\n\r\n <!-- Notification content -->\r\n <div class=\"flex-1 min-w-0\">\r\n <h4 class=\"text-xs font-semibold text-[var(--foreground)] truncate\" [class.font-bold]=\"!item.read\">\r\n {{ item.title }}\r\n </h4>\r\n <p class=\"text-[10.5px] text-[var(--secondary)] mt-0.5 leading-snug break-words\">\r\n {{ item.message }}\r\n </p>\r\n <span class=\"block text-[9px] text-[var(--secondary)] mt-1 font-medium\">\r\n {{ formatTime(item.createdAt) }}\r\n </span>\r\n </div>\r\n\r\n <!-- Actions -->\r\n @if (!item.read) {\r\n <div class=\"flex items-center flex-shrink-0\">\r\n <button\r\n type=\"button\"\r\n class=\"mark-read-btn grid h-6 w-6 place-items-center rounded-full text-[var(--secondary)] hover:text-[var(--primary)] hover:bg-[rgba(var(--foreground),0.05)] cursor-pointer\"\r\n [title]=\"'Mark as read'\"\r\n (click)=\"onMarkRead(item.id, $event)\"\r\n >\r\n <svg lucideCheckCheck class=\"h-3.5 w-3.5\"></svg>\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%}.notification-item{border-left:2px solid transparent}.notification-item:hover{border-left-color:var(--primary)}.mark-read-btn{opacity:.5;transition:opacity .2s,background-color .2s}.notification-item:hover .mark-read-btn{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideBellOff, selector: "svg[lucideBellOff]" }, { kind: "component", type: LucideCheckCheck, selector: "svg[lucideCheckCheck]" }, { kind: "component", type: LucideTrash2, selector: "svg[lucideTrash2]" }] });
240
230
  }
241
231
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NotificationPanelComponent, decorators: [{
242
232
  type: Component,
243
- args: [{ selector: 'lib-notification-panel', standalone: true, imports: [CommonModule, LucideAngularModule], template: "<div class=\"notification-panel-inner flex flex-col h-full max-h-[380px] w-full text-left\">\n <!-- Panel Header -->\n <div class=\"flex items-center justify-between border-b border-[var(--border)] px-3.5 py-2\">\n <span class=\"text-[10px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Notifications</span>\n @if (notifications().length > 0) {\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-[9px] font-bold text-[var(--secondary)] hover:text-[var(--danger,#ef4444)] hover:underline cursor-pointer\"\n (click)=\"onClearAll($event)\"\n >\n <lucide-icon [name]=\"trashIcon\" class=\"h-3 w-3\" />\n <span>Clear All</span>\n </button>\n }\n </div>\n\n <!-- Panel Body -->\n <div class=\"flex-1 overflow-y-auto min-h-0 py-0.5 divide-y divide-[var(--border)]\">\n @if (notifications().length === 0) {\n <div class=\"flex flex-col items-center justify-center py-10 px-4 text-center select-none\">\n <div class=\"flex h-10 w-10 items-center justify-center rounded-full bg-[rgba(var(--secondary),0.06)] text-[var(--secondary)] mb-2.5\">\n <lucide-icon [name]=\"bellOffIcon\" class=\"h-4.5 w-4.5\" />\n </div>\n <p class=\"text-xs font-semibold text-[var(--foreground)]\">All caught up</p>\n <p class=\"text-[10px] text-[var(--secondary)] mt-0.5\">No new notifications to display.</p>\n </div>\n } @else {\n @for (item of notifications(); track item.id) {\n <div\n (click)=\"onNotificationClick(item)\"\n class=\"notification-item flex gap-2.5 p-3 transition hover:bg-[rgba(var(--foreground),0.02)] cursor-pointer relative\"\n [class.bg-[rgba(var(--primary),0.015)]]=\"!item.read\"\n >\n <!-- Read status dot -->\n <div class=\"flex-shrink-0 pt-1\">\n @if (!item.read) {\n <span class=\"block h-1.5 w-1.5 rounded-full bg-[var(--primary)] animate-pulse\"></span>\n } @else {\n <span class=\"block h-1.5 w-1.5 rounded-full bg-transparent\"></span>\n }\n </div>\n\n <!-- Notification content -->\n <div class=\"flex-1 min-w-0\">\n <h4 class=\"text-xs font-semibold text-[var(--foreground)] truncate\" [class.font-bold]=\"!item.read\">\n {{ item.title }}\n </h4>\n <p class=\"text-[10.5px] text-[var(--secondary)] mt-0.5 leading-snug break-words\">\n {{ item.message }}\n </p>\n <span class=\"block text-[9px] text-[var(--secondary)] mt-1 font-medium\">\n {{ formatTime(item.createdAt) }}\n </span>\n </div>\n\n <!-- Actions -->\n @if (!item.read) {\n <div class=\"flex items-center flex-shrink-0\">\n <button\n type=\"button\"\n class=\"mark-read-btn grid h-6 w-6 place-items-center rounded-full text-[var(--secondary)] hover:text-[var(--primary)] hover:bg-[rgba(var(--foreground),0.05)] cursor-pointer\"\n [title]=\"'Mark as read'\"\n (click)=\"onMarkRead(item.id, $event)\"\n >\n <lucide-icon [name]=\"checkCheckIcon\" class=\"h-3.5 w-3.5\" />\n </button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%}.notification-item{border-left:2px solid transparent}.notification-item:hover{border-left-color:var(--primary)}.mark-read-btn{opacity:.5;transition:opacity .2s,background-color .2s}.notification-item:hover .mark-read-btn{opacity:1}\n"] }]
233
+ args: [{ selector: 'lib-notification-panel', standalone: true, imports: [CommonModule, LucideBellOff, LucideCheckCheck, LucideTrash2], template: "<div class=\"notification-panel-inner flex flex-col h-full max-h-[380px] w-full text-left\">\r\n <!-- Panel Header -->\r\n <div class=\"flex items-center justify-between border-b border-[var(--border)] px-3.5 py-2\">\r\n <span class=\"text-[10px] font-bold uppercase tracking-wider text-[var(--secondary)]\">Notifications</span>\r\n @if (notifications().length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center gap-1 text-[9px] font-bold text-[var(--secondary)] hover:text-[var(--danger,#ef4444)] hover:underline cursor-pointer\"\r\n (click)=\"onClearAll($event)\"\r\n >\r\n <svg lucideTrash2 class=\"h-3 w-3\"></svg>\n <span>Clear All</span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Panel Body -->\r\n <div class=\"flex-1 overflow-y-auto min-h-0 py-0.5 divide-y divide-[var(--border)]\">\r\n @if (notifications().length === 0) {\r\n <div class=\"flex flex-col items-center justify-center py-10 px-4 text-center select-none\">\r\n <div class=\"flex h-10 w-10 items-center justify-center rounded-full bg-[rgba(var(--secondary),0.06)] text-[var(--secondary)] mb-2.5\">\r\n <svg lucideBellOff class=\"h-4.5 w-4.5\"></svg>\n </div>\r\n <p class=\"text-xs font-semibold text-[var(--foreground)]\">All caught up</p>\r\n <p class=\"text-[10px] text-[var(--secondary)] mt-0.5\">No new notifications to display.</p>\r\n </div>\r\n } @else {\r\n @for (item of notifications(); track item.id) {\r\n <div\r\n (click)=\"onNotificationClick(item)\"\r\n class=\"notification-item flex gap-2.5 p-3 transition hover:bg-[rgba(var(--foreground),0.02)] cursor-pointer relative\"\r\n [class.bg-[rgba(var(--primary),0.015)]]=\"!item.read\"\r\n >\r\n <!-- Read status dot -->\r\n <div class=\"flex-shrink-0 pt-1\">\r\n @if (!item.read) {\r\n <span class=\"block h-1.5 w-1.5 rounded-full bg-[var(--primary)] animate-pulse\"></span>\r\n } @else {\r\n <span class=\"block h-1.5 w-1.5 rounded-full bg-transparent\"></span>\r\n }\r\n </div>\r\n\r\n <!-- Notification content -->\r\n <div class=\"flex-1 min-w-0\">\r\n <h4 class=\"text-xs font-semibold text-[var(--foreground)] truncate\" [class.font-bold]=\"!item.read\">\r\n {{ item.title }}\r\n </h4>\r\n <p class=\"text-[10.5px] text-[var(--secondary)] mt-0.5 leading-snug break-words\">\r\n {{ item.message }}\r\n </p>\r\n <span class=\"block text-[9px] text-[var(--secondary)] mt-1 font-medium\">\r\n {{ formatTime(item.createdAt) }}\r\n </span>\r\n </div>\r\n\r\n <!-- Actions -->\r\n @if (!item.read) {\r\n <div class=\"flex items-center flex-shrink-0\">\r\n <button\r\n type=\"button\"\r\n class=\"mark-read-btn grid h-6 w-6 place-items-center rounded-full text-[var(--secondary)] hover:text-[var(--primary)] hover:bg-[rgba(var(--foreground),0.05)] cursor-pointer\"\r\n [title]=\"'Mark as read'\"\r\n (click)=\"onMarkRead(item.id, $event)\"\r\n >\r\n <svg lucideCheckCheck class=\"h-3.5 w-3.5\"></svg>\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%}.notification-item{border-left:2px solid transparent}.notification-item:hover{border-left-color:var(--primary)}.mark-read-btn{opacity:.5;transition:opacity .2s,background-color .2s}.notification-item:hover .mark-read-btn{opacity:1}\n"] }]
244
234
  }], propDecorators: { notifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "notifications", required: false }] }], notificationClick: [{ type: i0.Output, args: ["notificationClick"] }], markRead: [{ type: i0.Output, args: ["markRead"] }], clearAll: [{ type: i0.Output, args: ["clearAll"] }] } });
245
235
 
246
236
  class NotificationBellComponent {
@@ -249,7 +239,6 @@ class NotificationBellComponent {
249
239
  notificationClick = output();
250
240
  markRead = output();
251
241
  clearAll = output();
252
- bellIcon = Bell;
253
242
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
254
243
  togglePanel() {
255
244
  this.isOpen.update((v) => !v);
@@ -274,11 +263,11 @@ class NotificationBellComponent {
274
263
  }
275
264
  }
276
265
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NotificationBellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
277
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: NotificationBellComponent, isStandalone: true, selector: "lib-notification-bell", inputs: { unreadCount: { classPropertyName: "unreadCount", publicName: "unreadCount", isSignal: true, isRequired: false, transformFunction: null }, notifications: { classPropertyName: "notifications", publicName: "notifications", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { notificationClick: "notificationClick", markRead: "markRead", clearAll: "clearAll" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, ngImport: i0, template: "<div class=\"notification-bell-wrapper relative inline-block text-left\">\n <button\n type=\"button\"\n class=\"relative grid h-9 w-9 place-items-center rounded-md border border-transparent text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"togglePanel()\"\n aria-haspopup=\"true\"\n [aria-expanded]=\"isOpen()\"\n aria-label=\"View notifications\"\n >\n <lucide-icon [name]=\"bellIcon\" class=\"h-4.5 w-4.5\" />\n \n @if (unreadCount() > 0) {\n <span class=\"absolute top-1.5 right-1.5 flex h-2 w-2\">\n <span class=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--danger,#ef4444)] opacity-75\"></span>\n <span class=\"relative inline-flex rounded-full h-2 w-2 bg-[var(--danger,#ef4444)]\"></span>\n </span>\n }\n </button>\n\n @if (isOpen()) {\n <div class=\"notification-dropdown absolute right-0 mt-2 w-80 origin-top-right rounded-lg border border-[var(--border)] bg-[var(--background)] p-0 shadow-lg focus:outline-none z-50\">\n <lib-notification-panel\n [notifications]=\"notifications()\"\n (notificationClick)=\"onNotificationClick($event)\"\n (markRead)=\"onMarkRead($event)\"\n (clearAll)=\"onClearAll()\"\n />\n </div>\n }\n</div>\n", styles: [":host{display:inline-block}.notification-dropdown{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:bellEnter .15s cubic-bezier(.16,1,.3,1);overflow:hidden}@keyframes bellEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: NotificationPanelComponent, selector: "lib-notification-panel", inputs: ["notifications"], outputs: ["notificationClick", "markRead", "clearAll"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
266
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: NotificationBellComponent, isStandalone: true, selector: "lib-notification-bell", inputs: { unreadCount: { classPropertyName: "unreadCount", publicName: "unreadCount", isSignal: true, isRequired: false, transformFunction: null }, notifications: { classPropertyName: "notifications", publicName: "notifications", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { notificationClick: "notificationClick", markRead: "markRead", clearAll: "clearAll" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, ngImport: i0, template: "<div class=\"notification-bell-wrapper relative inline-block text-left\">\r\n <button\n type=\"button\"\n class=\"notification-trigger\"\n (click)=\"togglePanel()\"\r\n aria-haspopup=\"true\"\r\n [aria-expanded]=\"isOpen()\"\r\n aria-label=\"View notifications\"\r\n >\r\n <svg lucideBell class=\"h-4.5 w-4.5\"></svg>\n \r\n @if (unreadCount() > 0) {\r\n <span class=\"absolute top-1.5 right-1.5 flex h-2 w-2\">\r\n <span class=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--danger,#ef4444)] opacity-75\"></span>\r\n <span class=\"relative inline-flex rounded-full h-2 w-2 bg-[var(--danger,#ef4444)]\"></span>\r\n </span>\r\n }\r\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"notification-dropdown fixed left-2 right-2 top-14 mt-2 origin-top rounded-xl border border-[var(--border)] bg-[var(--background)] p-0 shadow-lg focus:outline-none z-50 sm:absolute sm:left-auto sm:right-0 sm:top-auto sm:w-80 sm:origin-top-right\">\n <lib-notification-panel\r\n [notifications]=\"notifications()\"\r\n (notificationClick)=\"onNotificationClick($event)\"\r\n (markRead)=\"onMarkRead($event)\"\r\n (clearAll)=\"onClearAll()\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.notification-trigger{position:relative;display:grid;width:2.15rem;height:2.15rem;place-items:center;border:1px solid transparent;border-radius:.5rem;color:var(--foreground);background:transparent;cursor:pointer;transition:background-color .15s ease,border-color .15s ease}.notification-trigger:hover{border-color:var(--border);background:color-mix(in srgb,var(--border) 35%,transparent)}.notification-trigger:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.notification-dropdown{box-shadow:0 12px 30px #0f172a24;animation:bellEnter .15s cubic-bezier(.16,1,.3,1);overflow:hidden}@keyframes bellEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: NotificationPanelComponent, selector: "lib-notification-panel", inputs: ["notifications"], outputs: ["notificationClick", "markRead", "clearAll"] }, { kind: "component", type: LucideBell, selector: "svg[lucideBell]" }] });
278
267
  }
279
268
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NotificationBellComponent, decorators: [{
280
269
  type: Component,
281
- args: [{ selector: 'lib-notification-bell', standalone: true, imports: [CommonModule, NotificationPanelComponent, LucideAngularModule], template: "<div class=\"notification-bell-wrapper relative inline-block text-left\">\n <button\n type=\"button\"\n class=\"relative grid h-9 w-9 place-items-center rounded-md border border-transparent text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"togglePanel()\"\n aria-haspopup=\"true\"\n [aria-expanded]=\"isOpen()\"\n aria-label=\"View notifications\"\n >\n <lucide-icon [name]=\"bellIcon\" class=\"h-4.5 w-4.5\" />\n \n @if (unreadCount() > 0) {\n <span class=\"absolute top-1.5 right-1.5 flex h-2 w-2\">\n <span class=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--danger,#ef4444)] opacity-75\"></span>\n <span class=\"relative inline-flex rounded-full h-2 w-2 bg-[var(--danger,#ef4444)]\"></span>\n </span>\n }\n </button>\n\n @if (isOpen()) {\n <div class=\"notification-dropdown absolute right-0 mt-2 w-80 origin-top-right rounded-lg border border-[var(--border)] bg-[var(--background)] p-0 shadow-lg focus:outline-none z-50\">\n <lib-notification-panel\n [notifications]=\"notifications()\"\n (notificationClick)=\"onNotificationClick($event)\"\n (markRead)=\"onMarkRead($event)\"\n (clearAll)=\"onClearAll()\"\n />\n </div>\n }\n</div>\n", styles: [":host{display:inline-block}.notification-dropdown{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:bellEnter .15s cubic-bezier(.16,1,.3,1);overflow:hidden}@keyframes bellEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
270
+ args: [{ selector: 'lib-notification-bell', standalone: true, imports: [CommonModule, NotificationPanelComponent, LucideBell], template: "<div class=\"notification-bell-wrapper relative inline-block text-left\">\r\n <button\n type=\"button\"\n class=\"notification-trigger\"\n (click)=\"togglePanel()\"\r\n aria-haspopup=\"true\"\r\n [aria-expanded]=\"isOpen()\"\r\n aria-label=\"View notifications\"\r\n >\r\n <svg lucideBell class=\"h-4.5 w-4.5\"></svg>\n \r\n @if (unreadCount() > 0) {\r\n <span class=\"absolute top-1.5 right-1.5 flex h-2 w-2\">\r\n <span class=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--danger,#ef4444)] opacity-75\"></span>\r\n <span class=\"relative inline-flex rounded-full h-2 w-2 bg-[var(--danger,#ef4444)]\"></span>\r\n </span>\r\n }\r\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"notification-dropdown fixed left-2 right-2 top-14 mt-2 origin-top rounded-xl border border-[var(--border)] bg-[var(--background)] p-0 shadow-lg focus:outline-none z-50 sm:absolute sm:left-auto sm:right-0 sm:top-auto sm:w-80 sm:origin-top-right\">\n <lib-notification-panel\r\n [notifications]=\"notifications()\"\r\n (notificationClick)=\"onNotificationClick($event)\"\r\n (markRead)=\"onMarkRead($event)\"\r\n (clearAll)=\"onClearAll()\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.notification-trigger{position:relative;display:grid;width:2.15rem;height:2.15rem;place-items:center;border:1px solid transparent;border-radius:.5rem;color:var(--foreground);background:transparent;cursor:pointer;transition:background-color .15s ease,border-color .15s ease}.notification-trigger:hover{border-color:var(--border);background:color-mix(in srgb,var(--border) 35%,transparent)}.notification-trigger:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.notification-dropdown{box-shadow:0 12px 30px #0f172a24;animation:bellEnter .15s cubic-bezier(.16,1,.3,1);overflow:hidden}@keyframes bellEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
282
271
  }], propDecorators: { unreadCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "unreadCount", required: false }] }], notifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "notifications", required: false }] }], notificationClick: [{ type: i0.Output, args: ["notificationClick"] }], markRead: [{ type: i0.Output, args: ["markRead"] }], clearAll: [{ type: i0.Output, args: ["clearAll"] }], onOutsideClick: [{
283
272
  type: HostListener,
284
273
  args: ['document:click', ['$event']]
@@ -306,22 +295,16 @@ class UserAvatarComponent {
306
295
  this.imageError = true;
307
296
  }
308
297
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: UserAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
309
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: UserAvatarComponent, isStandalone: true, selector: "lib-user-avatar", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"avatar-container flex h-full w-full items-center justify-center rounded-full border border-[var(--border)] select-none overflow-hidden transition-all duration-200\">\n @if (user()?.avatar && !imageError) {\n <img [src]=\"user()?.avatar\" [alt]=\"user()?.name || 'User avatar'\" class=\"avatar-img h-full w-full object-cover\" (error)=\"onImageError()\" />\n } @else {\n <span class=\"avatar-initials text-xs font-semibold uppercase leading-none\">{{ initials() }}</span>\n }\n</div>\n", styles: [":host{display:block;width:inherit;height:inherit}.avatar-container{background:linear-gradient(135deg,var(--primary, #0067b8) 0%,var(--secondary, #005a9e) 100%);color:var(--background, #ffffff);box-shadow:0 1px 3px #0000000d}:host:hover .avatar-container{transform:scale(1.02);box-shadow:0 4px 6px -1px #00000014,0 2px 4px -1px #0000000a}.avatar-initials{text-shadow:0 1px 1px rgba(0,0,0,.15)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
298
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: UserAvatarComponent, isStandalone: true, selector: "lib-user-avatar", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"avatar-container flex h-full w-full items-center justify-center rounded-full border border-[var(--border)] select-none overflow-hidden transition-all duration-200\">\r\n @if (user()?.avatar && !imageError) {\r\n <img [src]=\"user()?.avatar\" [alt]=\"user()?.name || 'User avatar'\" class=\"avatar-img h-full w-full object-cover\" (error)=\"onImageError()\" />\r\n } @else {\r\n <span class=\"avatar-initials text-xs font-semibold uppercase leading-none\">{{ initials() }}</span>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:inherit;height:inherit}.avatar-container{background:linear-gradient(135deg,var(--primary, #0067b8) 0%,var(--secondary, #005a9e) 100%);color:var(--background, #ffffff);box-shadow:0 1px 3px #0000000d}:host:hover .avatar-container{transform:scale(1.02);box-shadow:0 4px 6px -1px #00000014,0 2px 4px -1px #0000000a}.avatar-initials{text-shadow:0 1px 1px rgba(0,0,0,.15)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
310
299
  }
311
300
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: UserAvatarComponent, decorators: [{
312
301
  type: Component,
313
- args: [{ selector: 'lib-user-avatar', standalone: true, imports: [CommonModule], template: "<div class=\"avatar-container flex h-full w-full items-center justify-center rounded-full border border-[var(--border)] select-none overflow-hidden transition-all duration-200\">\n @if (user()?.avatar && !imageError) {\n <img [src]=\"user()?.avatar\" [alt]=\"user()?.name || 'User avatar'\" class=\"avatar-img h-full w-full object-cover\" (error)=\"onImageError()\" />\n } @else {\n <span class=\"avatar-initials text-xs font-semibold uppercase leading-none\">{{ initials() }}</span>\n }\n</div>\n", styles: [":host{display:block;width:inherit;height:inherit}.avatar-container{background:linear-gradient(135deg,var(--primary, #0067b8) 0%,var(--secondary, #005a9e) 100%);color:var(--background, #ffffff);box-shadow:0 1px 3px #0000000d}:host:hover .avatar-container{transform:scale(1.02);box-shadow:0 4px 6px -1px #00000014,0 2px 4px -1px #0000000a}.avatar-initials{text-shadow:0 1px 1px rgba(0,0,0,.15)}\n"] }]
302
+ args: [{ selector: 'lib-user-avatar', standalone: true, imports: [CommonModule], template: "<div class=\"avatar-container flex h-full w-full items-center justify-center rounded-full border border-[var(--border)] select-none overflow-hidden transition-all duration-200\">\r\n @if (user()?.avatar && !imageError) {\r\n <img [src]=\"user()?.avatar\" [alt]=\"user()?.name || 'User avatar'\" class=\"avatar-img h-full w-full object-cover\" (error)=\"onImageError()\" />\r\n } @else {\r\n <span class=\"avatar-initials text-xs font-semibold uppercase leading-none\">{{ initials() }}</span>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:inherit;height:inherit}.avatar-container{background:linear-gradient(135deg,var(--primary, #0067b8) 0%,var(--secondary, #005a9e) 100%);color:var(--background, #ffffff);box-shadow:0 1px 3px #0000000d}:host:hover .avatar-container{transform:scale(1.02);box-shadow:0 4px 6px -1px #00000014,0 2px 4px -1px #0000000a}.avatar-initials{text-shadow:0 1px 1px rgba(0,0,0,.15)}\n"] }]
314
303
  }], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }] } });
315
304
 
316
305
  class UserMenuComponent {
317
306
  user = input(null, ...(ngDevMode ? [{ debugName: "user" }] : /* istanbul ignore next */ []));
318
307
  menuAction = output();
319
- // Lucide Icons mapping
320
- userIcon = User;
321
- settingsIcon = Settings;
322
- creditCardIcon = CreditCard;
323
- logOutIcon = LogOut;
324
- chevronDownIcon = ChevronDown;
325
308
  // Signalled open state
326
309
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
327
310
  toggleMenu() {
@@ -341,60 +324,106 @@ class UserMenuComponent {
341
324
  }
342
325
  }
343
326
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: UserMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
344
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: UserMenuComponent, isStandalone: true, selector: "lib-user-menu", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuAction: "menuAction" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, ngImport: i0, template: "<div class=\"user-menu-wrapper relative inline-block text-left\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1.5 rounded-full border border-[var(--border)] p-0.5 pr-2 transition-all hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:ring-offset-2 cursor-pointer\"\n (click)=\"toggleMenu()\"\n aria-haspopup=\"true\"\n [aria-expanded]=\"isOpen()\"\n aria-label=\"User profile menu\"\n >\n <div class=\"h-7 w-7 rounded-full overflow-hidden\">\n <lib-user-avatar [user]=\"user()\" />\n </div>\n <lucide-icon [name]=\"chevronDownIcon\" class=\"h-3.5 w-3.5 text-[var(--secondary)] transition-transform duration-200\" [class.rotate-180]=\"isOpen()\" />\n </button>\n\n @if (isOpen()) {\n <div class=\"user-dropdown-panel absolute right-0 mt-2 w-56 origin-top-right rounded-lg border border-[var(--border)] bg-[var(--background)] p-1.5 shadow-lg focus:outline-none z-50\">\n @if (user(); as u) {\n <div class=\"px-3 py-2 border-b border-[var(--border)] mb-1\">\n <p class=\"text-[10px] font-normal text-[var(--secondary)] uppercase tracking-wider\">Signed in as</p>\n <p class=\"text-sm font-semibold truncate text-[var(--foreground)]\" [title]=\"u.name\">{{ u.name }}</p>\n <p class=\"text-xs truncate text-[var(--secondary)] mt-0.5\" [title]=\"u.email\">{{ u.email }}</p>\n </div>\n }\n\n <div class=\"py-0.5 space-y-0.5\">\n <button\n type=\"button\"\n class=\"flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\n (click)=\"onAction('profile')\"\n >\n <lucide-icon [name]=\"userIcon\" class=\"h-4 w-4 text-[var(--secondary)]\" />\n <span>My Profile</span>\n </button>\n\n <button\n type=\"button\"\n class=\"flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\n (click)=\"onAction('settings')\"\n >\n <lucide-icon [name]=\"settingsIcon\" class=\"h-4 w-4 text-[var(--secondary)]\" />\n <span>Settings</span>\n </button>\n\n <button\n type=\"button\"\n class=\"flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\n (click)=\"onAction('billing')\"\n >\n <lucide-icon [name]=\"creditCardIcon\" class=\"h-4 w-4 text-[var(--secondary)]\" />\n <span>Billing</span>\n </button>\n </div>\n\n <div class=\"border-t border-[var(--border)] mt-1 pt-1\">\n <button\n type=\"button\"\n class=\"flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm text-[var(--danger,#ef4444)] transition hover:bg-[rgba(239,68,68,0.08)] text-left cursor-pointer\"\n (click)=\"onAction('logout')\"\n >\n <lucide-icon [name]=\"logOutIcon\" class=\"h-4 w-4 text-[var(--danger,#ef4444)]\" />\n <span>Sign Out</span>\n </button>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:inline-block}.user-dropdown-panel{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:dropdownEnter .15s cubic-bezier(.16,1,.3,1)}@keyframes dropdownEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UserAvatarComponent, selector: "lib-user-avatar", inputs: ["user"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
327
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: UserMenuComponent, isStandalone: true, selector: "lib-user-menu", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuAction: "menuAction" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, ngImport: i0, template: "<div class=\"user-menu-wrapper relative inline-block text-left\">\r\n <button type=\"button\" class=\"user-menu-trigger\" (click)=\"toggleMenu()\" aria-haspopup=\"true\" [aria-expanded]=\"isOpen()\"\r\n aria-label=\"User profile menu\">\r\n <div class=\"h-7 w-7 rounded-full overflow-hidden\">\r\n <lib-user-avatar [user]=\"user()\" />\r\n </div>\r\n <svg lucideChevronDown class=\"h-3.5 w-3.5 text-[var(--secondary)] transition-transform duration-200\"\n [class.rotate-180]=\"isOpen()\"></svg>\n </button>\r\n\r\n @if (isOpen()) {\r\n <div\r\n class=\"user-dropdown-panel fixed left-2 right-2 top-14 mt-2 origin-top rounded-xl border border-[var(--border)] bg-[var(--background)] p-1.5 shadow-lg focus:outline-none z-50 sm:absolute sm:left-auto sm:right-0 sm:top-auto sm:w-56 sm:origin-top-right\">\r\n @if (user(); as u) {\r\n <div class=\"px-3 py-2 border-b border-[var(--border)] mb-1\">\r\n <p class=\"text-sm font-semibold truncate text-[var(--foreground)]\" [title]=\"u.name\">{{ u.name }}</p>\r\n <p class=\"text-xs truncate text-[var(--secondary)] mt-0.5\" [title]=\"u.email\">{{ u.email }}</p>\r\n </div>\r\n }\r\n\r\n <div class=\"py-0.5 space-y-0.5\">\r\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('profile')\">\r\n <svg lucideUser class=\"h-4 w-4 text-[var(--secondary)]\"></svg>\n <span>My Profile</span>\r\n </button>\r\n\r\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('settings')\">\r\n <svg lucideSettings class=\"h-4 w-4 text-[var(--secondary)]\"></svg>\n <span>Settings</span>\r\n </button>\r\n\r\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('billing')\">\r\n <svg lucideCreditCard class=\"h-4 w-4 text-[var(--secondary)]\"></svg>\n <span>Billing</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"border-t border-[var(--border)] mt-1 pt-1\">\r\n <button type=\"button\" class=\"user-menu-item danger\" (click)=\"onAction('logout')\">\r\n <svg lucideLogOut class=\"h-4 w-4 text-[var(--danger,#ef4444)]\"></svg>\n <span>Sign Out</span>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n</div>\n", styles: [":host{display:inline-block}.user-menu-trigger{display:flex;align-items:center;gap:.375rem;border:1px solid var(--border);border-radius:999px;color:var(--foreground);background:var(--background);padding:.125rem .45rem .125rem .125rem;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.user-menu-trigger:hover{background:color-mix(in srgb,var(--border) 28%,transparent)}.user-menu-trigger:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.user-dropdown-panel{box-shadow:0 12px 30px #0f172a24;animation:dropdownEnter .15s cubic-bezier(.16,1,.3,1)}.user-menu-item{display:flex;width:100%;align-items:center;gap:.625rem;border:0;border-radius:.5rem;color:var(--foreground);background:transparent;padding:.55rem .75rem;font-size:.875rem;text-align:left;cursor:pointer;transition:background-color .15s ease,color .15s ease}.user-menu-item:hover{background:color-mix(in srgb,var(--border) 32%,transparent)}.user-menu-item.danger{color:var(--danger, #ef4444)}.user-menu-item.danger:hover{background:color-mix(in srgb,var(--danger, #ef4444) 10%,transparent)}@keyframes dropdownEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UserAvatarComponent, selector: "lib-user-avatar", inputs: ["user"] }, { kind: "component", type: LucideChevronDown, selector: "svg[lucideChevronDown]" }, { kind: "component", type: LucideCreditCard, selector: "svg[lucideCreditCard]" }, { kind: "component", type: LucideLogOut, selector: "svg[lucideLogOut]" }, { kind: "component", type: LucideSettings, selector: "svg[lucideSettings]" }, { kind: "component", type: LucideUser, selector: "svg[lucideUser]" }] });
345
328
  }
346
329
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: UserMenuComponent, decorators: [{
347
330
  type: Component,
348
- args: [{ selector: 'lib-user-menu', standalone: true, imports: [CommonModule, UserAvatarComponent, LucideAngularModule], template: "<div class=\"user-menu-wrapper relative inline-block text-left\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1.5 rounded-full border border-[var(--border)] p-0.5 pr-2 transition-all hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:ring-offset-2 cursor-pointer\"\n (click)=\"toggleMenu()\"\n aria-haspopup=\"true\"\n [aria-expanded]=\"isOpen()\"\n aria-label=\"User profile menu\"\n >\n <div class=\"h-7 w-7 rounded-full overflow-hidden\">\n <lib-user-avatar [user]=\"user()\" />\n </div>\n <lucide-icon [name]=\"chevronDownIcon\" class=\"h-3.5 w-3.5 text-[var(--secondary)] transition-transform duration-200\" [class.rotate-180]=\"isOpen()\" />\n </button>\n\n @if (isOpen()) {\n <div class=\"user-dropdown-panel absolute right-0 mt-2 w-56 origin-top-right rounded-lg border border-[var(--border)] bg-[var(--background)] p-1.5 shadow-lg focus:outline-none z-50\">\n @if (user(); as u) {\n <div class=\"px-3 py-2 border-b border-[var(--border)] mb-1\">\n <p class=\"text-[10px] font-normal text-[var(--secondary)] uppercase tracking-wider\">Signed in as</p>\n <p class=\"text-sm font-semibold truncate text-[var(--foreground)]\" [title]=\"u.name\">{{ u.name }}</p>\n <p class=\"text-xs truncate text-[var(--secondary)] mt-0.5\" [title]=\"u.email\">{{ u.email }}</p>\n </div>\n }\n\n <div class=\"py-0.5 space-y-0.5\">\n <button\n type=\"button\"\n class=\"flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\n (click)=\"onAction('profile')\"\n >\n <lucide-icon [name]=\"userIcon\" class=\"h-4 w-4 text-[var(--secondary)]\" />\n <span>My Profile</span>\n </button>\n\n <button\n type=\"button\"\n class=\"flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\n (click)=\"onAction('settings')\"\n >\n <lucide-icon [name]=\"settingsIcon\" class=\"h-4 w-4 text-[var(--secondary)]\" />\n <span>Settings</span>\n </button>\n\n <button\n type=\"button\"\n class=\"flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm text-[var(--foreground)] transition hover:bg-[rgba(var(--foreground),0.05)] text-left cursor-pointer\"\n (click)=\"onAction('billing')\"\n >\n <lucide-icon [name]=\"creditCardIcon\" class=\"h-4 w-4 text-[var(--secondary)]\" />\n <span>Billing</span>\n </button>\n </div>\n\n <div class=\"border-t border-[var(--border)] mt-1 pt-1\">\n <button\n type=\"button\"\n class=\"flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm text-[var(--danger,#ef4444)] transition hover:bg-[rgba(239,68,68,0.08)] text-left cursor-pointer\"\n (click)=\"onAction('logout')\"\n >\n <lucide-icon [name]=\"logOutIcon\" class=\"h-4 w-4 text-[var(--danger,#ef4444)]\" />\n <span>Sign Out</span>\n </button>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:inline-block}.user-dropdown-panel{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;animation:dropdownEnter .15s cubic-bezier(.16,1,.3,1)}@keyframes dropdownEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
331
+ args: [{ selector: 'lib-user-menu', standalone: true, imports: [CommonModule, UserAvatarComponent, LucideChevronDown, LucideCreditCard, LucideLogOut, LucideSettings, LucideUser], template: "<div class=\"user-menu-wrapper relative inline-block text-left\">\r\n <button type=\"button\" class=\"user-menu-trigger\" (click)=\"toggleMenu()\" aria-haspopup=\"true\" [aria-expanded]=\"isOpen()\"\r\n aria-label=\"User profile menu\">\r\n <div class=\"h-7 w-7 rounded-full overflow-hidden\">\r\n <lib-user-avatar [user]=\"user()\" />\r\n </div>\r\n <svg lucideChevronDown class=\"h-3.5 w-3.5 text-[var(--secondary)] transition-transform duration-200\"\n [class.rotate-180]=\"isOpen()\"></svg>\n </button>\r\n\r\n @if (isOpen()) {\r\n <div\r\n class=\"user-dropdown-panel fixed left-2 right-2 top-14 mt-2 origin-top rounded-xl border border-[var(--border)] bg-[var(--background)] p-1.5 shadow-lg focus:outline-none z-50 sm:absolute sm:left-auto sm:right-0 sm:top-auto sm:w-56 sm:origin-top-right\">\r\n @if (user(); as u) {\r\n <div class=\"px-3 py-2 border-b border-[var(--border)] mb-1\">\r\n <p class=\"text-sm font-semibold truncate text-[var(--foreground)]\" [title]=\"u.name\">{{ u.name }}</p>\r\n <p class=\"text-xs truncate text-[var(--secondary)] mt-0.5\" [title]=\"u.email\">{{ u.email }}</p>\r\n </div>\r\n }\r\n\r\n <div class=\"py-0.5 space-y-0.5\">\r\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('profile')\">\r\n <svg lucideUser class=\"h-4 w-4 text-[var(--secondary)]\"></svg>\n <span>My Profile</span>\r\n </button>\r\n\r\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('settings')\">\r\n <svg lucideSettings class=\"h-4 w-4 text-[var(--secondary)]\"></svg>\n <span>Settings</span>\r\n </button>\r\n\r\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('billing')\">\r\n <svg lucideCreditCard class=\"h-4 w-4 text-[var(--secondary)]\"></svg>\n <span>Billing</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"border-t border-[var(--border)] mt-1 pt-1\">\r\n <button type=\"button\" class=\"user-menu-item danger\" (click)=\"onAction('logout')\">\r\n <svg lucideLogOut class=\"h-4 w-4 text-[var(--danger,#ef4444)]\"></svg>\n <span>Sign Out</span>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n</div>\n", styles: [":host{display:inline-block}.user-menu-trigger{display:flex;align-items:center;gap:.375rem;border:1px solid var(--border);border-radius:999px;color:var(--foreground);background:var(--background);padding:.125rem .45rem .125rem .125rem;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.user-menu-trigger:hover{background:color-mix(in srgb,var(--border) 28%,transparent)}.user-menu-trigger:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.user-dropdown-panel{box-shadow:0 12px 30px #0f172a24;animation:dropdownEnter .15s cubic-bezier(.16,1,.3,1)}.user-menu-item{display:flex;width:100%;align-items:center;gap:.625rem;border:0;border-radius:.5rem;color:var(--foreground);background:transparent;padding:.55rem .75rem;font-size:.875rem;text-align:left;cursor:pointer;transition:background-color .15s ease,color .15s ease}.user-menu-item:hover{background:color-mix(in srgb,var(--border) 32%,transparent)}.user-menu-item.danger{color:var(--danger, #ef4444)}.user-menu-item.danger:hover{background:color-mix(in srgb,var(--danger, #ef4444) 10%,transparent)}@keyframes dropdownEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}\n"] }]
349
332
  }], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }], menuAction: [{ type: i0.Output, args: ["menuAction"] }], onOutsideClick: [{
350
333
  type: HostListener,
351
334
  args: ['document:click', ['$event']]
352
335
  }] } });
353
336
 
354
337
  // lib/shared/icon/icon.component.ts
355
- const ICONS = {
356
- search: Search,
357
- menu: Menu,
358
- x: X,
359
- sun: Sun,
360
- moon: Moon,
361
- monitor: Monitor,
362
- bell: Bell,
363
- grid: Grid,
364
- chevronDown: ChevronDown,
365
- building2: Building2
366
- };
367
338
  class IconComponent {
368
339
  name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
369
340
  size = input(24, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
370
341
  color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
371
342
  strokeWidth = input(undefined, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
372
- iconName = computed(() => {
373
- const name = this.name();
374
- return ICONS[name] || Search;
375
- }, ...(ngDevMode ? [{ debugName: "iconName" }] : /* istanbul ignore next */ []));
376
343
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
377
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.15", type: IconComponent, isStandalone: true, selector: "lib-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
378
- <lucide-icon
379
- [name]="iconName()"
380
- [size]="size()"
381
- [color]="color()"
382
- [strokeWidth]="strokeWidth()"
383
- class="lib-icon"
384
- />
385
- `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center}.lib-icon{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
344
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: IconComponent, isStandalone: true, selector: "lib-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
345
+ @switch (name()) {
346
+ @case ('menu') {
347
+ <svg lucideMenu [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
348
+ }
349
+ @case ('x') {
350
+ <svg lucideX [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
351
+ }
352
+ @case ('sun') {
353
+ <svg lucideSun [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
354
+ }
355
+ @case ('moon') {
356
+ <svg lucideMoon [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
357
+ }
358
+ @case ('monitor') {
359
+ <svg lucideMonitor [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
360
+ }
361
+ @case ('bell') {
362
+ <svg lucideBell [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
363
+ }
364
+ @case ('grid') {
365
+ <svg lucideGrid [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
366
+ }
367
+ @case ('chevronDown') {
368
+ <svg lucideChevronDown [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
369
+ }
370
+ @case ('building2') {
371
+ <svg lucideBuilding2 [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
372
+ }
373
+ @default {
374
+ <svg lucideSearch [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
375
+ }
376
+ }
377
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;color:inherit}.lib-icon{display:block;color:inherit}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideBell, selector: "svg[lucideBell]" }, { kind: "component", type: LucideBuilding2, selector: "svg[lucideBuilding2]" }, { kind: "component", type: LucideChevronDown, selector: "svg[lucideChevronDown]" }, { kind: "component", type: LucideGrid, selector: "svg[lucideGrid3x3], svg[lucideGrid], svg[lucideGrid3X3]" }, { kind: "component", type: LucideMenu, selector: "svg[lucideMenu]" }, { kind: "component", type: LucideMonitor, selector: "svg[lucideMonitor]" }, { kind: "component", type: LucideMoon, selector: "svg[lucideMoon]" }, { kind: "component", type: LucideSearch, selector: "svg[lucideSearch]" }, { kind: "component", type: LucideSun, selector: "svg[lucideSun]" }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }] });
386
378
  }
387
379
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: IconComponent, decorators: [{
388
380
  type: Component,
389
- args: [{ selector: 'lib-icon', standalone: true, imports: [CommonModule, LucideAngularModule], template: `
390
- <lucide-icon
391
- [name]="iconName()"
392
- [size]="size()"
393
- [color]="color()"
394
- [strokeWidth]="strokeWidth()"
395
- class="lib-icon"
396
- />
397
- `, styles: [":host{display:inline-flex;align-items:center;justify-content:center}.lib-icon{display:block}\n"] }]
381
+ args: [{ selector: 'lib-icon', standalone: true, imports: [
382
+ CommonModule,
383
+ LucideBell,
384
+ LucideBuilding2,
385
+ LucideChevronDown,
386
+ LucideGrid,
387
+ LucideMenu,
388
+ LucideMonitor,
389
+ LucideMoon,
390
+ LucideSearch,
391
+ LucideSun,
392
+ LucideX,
393
+ ], template: `
394
+ @switch (name()) {
395
+ @case ('menu') {
396
+ <svg lucideMenu [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
397
+ }
398
+ @case ('x') {
399
+ <svg lucideX [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
400
+ }
401
+ @case ('sun') {
402
+ <svg lucideSun [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
403
+ }
404
+ @case ('moon') {
405
+ <svg lucideMoon [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
406
+ }
407
+ @case ('monitor') {
408
+ <svg lucideMonitor [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
409
+ }
410
+ @case ('bell') {
411
+ <svg lucideBell [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
412
+ }
413
+ @case ('grid') {
414
+ <svg lucideGrid [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
415
+ }
416
+ @case ('chevronDown') {
417
+ <svg lucideChevronDown [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
418
+ }
419
+ @case ('building2') {
420
+ <svg lucideBuilding2 [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
421
+ }
422
+ @default {
423
+ <svg lucideSearch [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
424
+ }
425
+ }
426
+ `, styles: [":host{display:inline-flex;align-items:center;justify-content:center;color:inherit}.lib-icon{display:block;color:inherit}\n"] }]
398
427
  }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }] } });
399
428
 
400
429
  class HeaderComponent {
@@ -409,10 +438,17 @@ class HeaderComponent {
409
438
  applications = input([], ...(ngDevMode ? [{ debugName: "applications" }] : /* istanbul ignore next */ []));
410
439
  notifications = input([], ...(ngDevMode ? [{ debugName: "notifications" }] : /* istanbul ignore next */ []));
411
440
  notificationCount = input(0, ...(ngDevMode ? [{ debugName: "notificationCount" }] : /* istanbul ignore next */ []));
412
- logoUrl = input('https://cdn.jsdelivr.net/gh/Excellore/excellore-static-assets@main/images/logo_origin_tp.PNG', ...(ngDevMode ? [{ debugName: "logoUrl" }] : /* istanbul ignore next */ []));
441
+ logoUrl = input('https://cdn.jsdelivr.net/gh/Excellore/excellore-static-assets@main/images/excellore-logo-origin.png', ...(ngDevMode ? [{ debugName: "logoUrl" }] : /* istanbul ignore next */ []));
413
442
  appName = input('Excellore', ...(ngDevMode ? [{ debugName: "appName" }] : /* istanbul ignore next */ []));
443
+ currentAppName = input('', ...(ngDevMode ? [{ debugName: "currentAppName" }] : /* istanbul ignore next */ []));
444
+ showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
445
+ showNotifications = input(true, ...(ngDevMode ? [{ debugName: "showNotifications" }] : /* istanbul ignore next */ []));
446
+ showAppSwitcher = input(true, ...(ngDevMode ? [{ debugName: "showAppSwitcher" }] : /* istanbul ignore next */ []));
447
+ showThemeSwitcher = input(true, ...(ngDevMode ? [{ debugName: "showThemeSwitcher" }] : /* istanbul ignore next */ []));
448
+ showSignIn = input(true, ...(ngDevMode ? [{ debugName: "showSignIn" }] : /* istanbul ignore next */ []));
414
449
  // Outputs as decided in the architecture plan
415
450
  search = output();
451
+ signIn = output();
416
452
  applicationSelected = output();
417
453
  notificationClick = output();
418
454
  markRead = output();
@@ -442,6 +478,7 @@ class HeaderComponent {
442
478
  return overrideCount;
443
479
  return this.notifications().filter((n) => !n.read).length;
444
480
  }, ...(ngDevMode ? [{ debugName: "unreadCount" }] : /* istanbul ignore next */ []));
481
+ hasMobileMenuContent = computed(() => this.showSearch() || this.navigationItems().some((item) => this.hasPermission(item)), ...(ngDevMode ? [{ debugName: "hasMobileMenuContent" }] : /* istanbul ignore next */ []));
445
482
  // Determines if an item's internal route is active
446
483
  isActive(item) {
447
484
  if (!item.route)
@@ -467,15 +504,6 @@ class HeaderComponent {
467
504
  onSearch(query) {
468
505
  this.search.emit(query);
469
506
  }
470
- getOrganizationInitials(organization) {
471
- return organization.name
472
- .split(/\s+/)
473
- .filter(Boolean)
474
- .slice(0, 2)
475
- .map((word) => word[0])
476
- .join('')
477
- .toUpperCase();
478
- }
479
507
  onApplicationSelect(app) {
480
508
  this.applicationSelected.emit(app);
481
509
  }
@@ -492,7 +520,7 @@ class HeaderComponent {
492
520
  this.menuAction.emit(action);
493
521
  }
494
522
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: HeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
495
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: HeaderComponent, isStandalone: true, selector: "lib-header", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: false, transformFunction: null }, organization: { classPropertyName: "organization", publicName: "organization", isSignal: true, isRequired: false, transformFunction: null }, navigationItems: { classPropertyName: "navigationItems", publicName: "navigationItems", isSignal: true, isRequired: false, transformFunction: null }, applications: { classPropertyName: "applications", publicName: "applications", isSignal: true, isRequired: false, transformFunction: null }, notifications: { classPropertyName: "notifications", publicName: "notifications", isSignal: true, isRequired: false, transformFunction: null }, notificationCount: { classPropertyName: "notificationCount", publicName: "notificationCount", isSignal: true, isRequired: false, transformFunction: null }, logoUrl: { classPropertyName: "logoUrl", publicName: "logoUrl", isSignal: true, isRequired: false, transformFunction: null }, appName: { classPropertyName: "appName", publicName: "appName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { search: "search", applicationSelected: "applicationSelected", notificationClick: "notificationClick", markRead: "markRead", clearAll: "clearAll", menuAction: "menuAction" }, ngImport: i0, template: "<header\n class=\"header-container fixed top-0 inset-x-0 z-40 flex h-14 items-center justify-between border-b border-[var(--border)] bg-[var(--background)]/80 px-4 text-[var(--foreground)] backdrop-blur-md\">\n\n <!-- Left Side: Organization & Desktop Links -->\n <div class=\"flex items-center gap-3.5\">\n\n <!-- Excellore brand logo/title hidden while the organization is the primary header identity.\n <a routerLink=\"/\"\n class=\"flex items-center gap-2 text-sm font-bold tracking-tight text-[var(--foreground)] no-underline hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-[var(--primary)] rounded\">\n <img [src]=\"logoUrl()\" [alt]=\"appName() + ' logo'\" width=\"26\" height=\"26\" class=\"h-6.5 w-6.5 select-none\" />\n <span class=\"hidden sm:inline-block\">{{ appName() }}</span>\n </a>\n -->\n\n <!-- Organization Identity -->\n @if (organization(); as organization) {\n <div class=\"h-8 flex items-center\">\n <div class=\"flex items-center gap-2.5\" [attr.aria-label]=\"'Organization: ' + organization.name\">\n @if (organization.logoUrl) {\n <img [src]=\"organization.logoUrl\" [alt]=\"organization.name + ' logo'\"\n class=\"h-8 w-8 rounded-md object-contain bg-white\" />\n } @else {\n <span\n class=\"grid h-8 w-8 place-items-center rounded-md bg-[var(--primary)] text-[11px] font-bold text-white\"\n aria-hidden=\"true\">\n {{ getOrganizationInitials(organization) }}\n </span>\n }\n <span class=\"hidden max-w-[180px] truncate text-sm font-semibold text-[var(--foreground)] sm:inline\">\n {{ organization.name }}\n </span>\n </div>\n </div>\n }\n\n <!-- Desktop Navigation Links -->\n <nav class=\"ml-2 hidden lg:flex items-center gap-5.5\" aria-label=\"Desktop primary navigation\">\n @for (item of navigationItems(); track item.id) {\n @if (hasPermission(item)) {\n @if (item.route) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{exact: true}\"\n class=\"nav-link text-xs font-medium text-[var(--secondary)] transition hover:text-[var(--foreground)] no-underline py-1 relative focus:outline-none focus:ring-2 focus:ring-[var(--primary)] rounded\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\">\n {{ item.label }}\n </a>\n } @else {\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\"\n class=\"nav-link text-xs font-medium text-[var(--secondary)] transition hover:text-[var(--foreground)] no-underline py-1 relative focus:outline-none focus:ring-2 focus:ring-[var(--primary)] rounded\">\n {{ item.label }}\n </a>\n }\n }\n }\n </nav>\n </div>\n\n <!-- Middle: Search Input (Desktop) -->\n <div class=\"hidden md:flex flex-1 max-w-[260px] mx-4\">\n <div class=\"relative w-full\">\n <!-- <lucide-icon [name]=\"searchIcon\"\n class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[var(--secondary)] pointer-events-none\"\n aria-hidden=\"true\" /> -->\n <lib-icon name=\"search\" [size]=\"14\"\n class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[var(--secondary)]\" />\n\n <input type=\"search\" placeholder=\"Search Excellore...\" #searchInput\n (keyup.enter)=\"onSearch(searchInput.value); searchInput.value = ''\"\n class=\"w-full h-8 pl-8 pr-3 text-xs bg-[rgba(var(--foreground),0.02)] border border-[var(--border)] rounded-md focus:outline-none focus:bg-[var(--background)] focus:ring-2 focus:ring-[var(--primary)] text-[var(--foreground)] transition-all placeholder:text-[var(--secondary)]/60\"\n autocomplete=\"off\" aria-label=\"Search\" />\n </div>\n </div>\n\n <!-- Right Side Actions -->\n <div class=\"flex items-center gap-1 sm:gap-1.5\">\n\n <!-- Mobile Search Trigger -->\n <button type=\"button\"\n class=\"grid md:hidden h-8.5 w-8.5 place-items-center rounded-md border border-transparent hover:bg-[rgba(var(--foreground),0.03)] text-[var(--foreground)] cursor-pointer focus:outline-none focus:ring-2 focus:ring-[var(--primary)]\"\n (click)=\"toggleMobileMenu()\" aria-label=\"Open mobile menu\" aria-expanded=\"isMobileMenuOpen()\" #menuToggle>\n <!-- <lucide-icon [name]=\"searchIcon\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" /> -->\n <lib-icon name=\"search\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\n </button>\n\n <!-- Theme Switcher -->\n <button type=\"button\"\n class=\"grid h-8.5 w-8.5 place-items-center rounded-md border border-transparent text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"themeService.toggleTheme()\" aria-label=\"Toggle theme\">\n @switch (themeService.theme()) {\n @case ('light') {\n <!-- <lucide-icon [name]=\"sunIcon\" class=\"h-4 w-4 text-[var(--primary)]\" aria-hidden=\"true\" /> -->\n <lib-icon name=\"sun\" [size]=\"16\" class=\"text-[var(--primary)]\" />\n }\n @case ('dark') {\n <!-- <lucide-icon [name]=\"moonIcon\" class=\"h-4 w-4 text-[var(--primary)]\" aria-hidden=\"true\" /> -->\n <lib-icon name=\"moon\" [size]=\"16\" class=\"text-[var(--primary)]\" />\n }\n @default {\n <!-- <lucide-icon [name]=\"monitorIcon\" class=\"h-4 w-4 text-[var(--secondary)]\" aria-hidden=\"true\" /> -->\n <lib-icon name=\"monitor\" [size]=\"16\" class=\"text-[var(--secondary)]\" />\n }\n }\n </button>\n\n <!-- App Switcher -->\n @if (applications().length > 0) {\n <lib-app-switcher [applications]=\"applications()\" (applicationSelected)=\"onApplicationSelect($event)\" />\n }\n\n <!-- Notifications -->\n <lib-notification-bell [unreadCount]=\"unreadCount()\" [notifications]=\"notifications()\"\n (notificationClick)=\"onNotificationClick($event)\" (markRead)=\"onMarkRead($event)\" (clearAll)=\"onClearAll()\" />\n\n <!-- User Menu -->\n @if (user()) {\n <div class=\"h-6 border-l border-[var(--border)] pl-1.5 sm:pl-2 flex items-center ml-0.5\">\n <lib-user-menu [user]=\"user()\" (menuAction)=\"onMenuAction($event)\" />\n </div>\n }\n\n <!-- Mobile Menu Toggle (Hamburger) -->\n <button type=\"button\"\n class=\"grid lg:hidden h-8.5 w-8.5 place-items-center rounded-md border border-transparent text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"toggleMobileMenu()\" aria-label=\"Toggle navigation menu\" aria-expanded=\"isMobileMenuOpen()\"\n #mobileMenuToggle>\n <!-- <lucide-icon [name]=\"isMobileMenuOpen() ? xIcon : menuIcon\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" /> -->\n <lib-icon [name]=\"isMobileMenuOpen() ? 'x' : 'menu'\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\n </button>\n </div>\n</header>\n\n<!-- Mobile Menu Backdrop -->\n@if (isMobileMenuOpen()) {\n<div class=\"fixed inset-0 z-30 bg-black/20 lg:hidden\" (click)=\"closeMobileMenu()\" (keydown.escape)=\"closeMobileMenu()\"\n tabindex=\"-1\" aria-hidden=\"true\"></div>\n\n<!-- Mobile Navigation Drawer -->\n<div\n class=\"mobile-drawer fixed top-14 inset-x-0 bottom-0 z-40 flex flex-col bg-[var(--background)] border-t border-[var(--border)] p-4 lg:hidden\"\n role=\"dialog\" aria-modal=\"true\" aria-label=\"Mobile navigation\" (keydown.escape)=\"closeMobileMenu()\">\n <!-- Mobile Search -->\n <div class=\"relative w-full mb-4\">\n <!-- <lucide-icon [name]=\"searchIcon\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--secondary)] pointer-events-none\"\n aria-hidden=\"true\" /> -->\n\n <lib-icon name=\"search\" [size]=\"16\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--secondary)] pointer-events-none\"\n aria-hidden=\"true\" />\n\n <input type=\"search\" placeholder=\"Search Excellore...\" #mobileSearchInput\n (keyup.enter)=\"onSearch(mobileSearchInput.value); mobileSearchInput.value = ''; closeMobileMenu()\"\n class=\"w-full h-10 pl-9 pr-3 text-sm bg-[rgba(var(--foreground),0.02)] border border-[var(--border)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--primary)] text-[var(--foreground)] placeholder:text-[var(--secondary)]/60\"\n autocomplete=\"off\" aria-label=\"Search\" />\n </div>\n\n <!-- Mobile Nav Links -->\n <nav class=\"flex flex-col gap-1 overflow-y-auto max-h-[70vh]\" role=\"navigation\">\n @for (item of navigationItems(); track item.id) {\n @if (hasPermission(item)) {\n @if (item.route) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"mobile-active\" [routerLinkActiveOptions]=\"{exact: true}\"\n (click)=\"closeMobileMenu()\"\n class=\"flex items-center px-3 py-2.5 rounded-lg text-sm font-semibold text-[var(--secondary)] hover:text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] no-underline focus:outline-none focus:ring-2 focus:ring-[var(--primary)]\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\">\n {{ item.label }}\n </a>\n } @else {\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" (click)=\"closeMobileMenu()\"\n class=\"flex items-center px-3 py-2.5 rounded-lg text-sm font-semibold text-[var(--secondary)] hover:text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] no-underline focus:outline-none focus:ring-2 focus:ring-[var(--primary)]\">\n {{ item.label }}\n </a>\n }\n }\n }\n </nav>\n</div>\n}\n", styles: [":host{display:block}.header-container{box-shadow:0 1px 2px #00000008;will-change:transform}.nav-link{transition:color .15s ease-in-out}.nav-link.active{color:var(--foreground)!important;font-weight:600}.nav-link.active:after{content:\"\";position:absolute;bottom:-14px;left:0;right:0;height:2px;background-color:rgb(var(--primary));border-radius:9999px}.mobile-drawer{box-shadow:0 10px 15px -3px #0000000d,0 4px 6px -2px #00000008;animation:slideDown .2s cubic-bezier(.16,1,.3,1)}.mobile-active{color:var(--foreground)!important;background-color:rgba(var(--foreground),.03);font-weight:700}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}:focus-visible{outline:2px solid rgb(var(--primary));outline-offset:2px}:focus:not(:focus-visible){outline:none}@supports not (backdrop-filter: blur(12px)){.header-container{background-color:rgb(var(--background))}}lucide-icon{display:inline-flex;vertical-align:middle}*{transition-property:background-color,border-color,color;transition-duration:.15s;transition-timing-function:ease-in-out}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: AppSwitcherComponent, selector: "lib-app-switcher", inputs: ["applications"], outputs: ["applicationSelected"] }, { kind: "component", type: NotificationBellComponent, selector: "lib-notification-bell", inputs: ["unreadCount", "notifications"], outputs: ["notificationClick", "markRead", "clearAll"] }, { kind: "component", type: UserMenuComponent, selector: "lib-user-menu", inputs: ["user"], outputs: ["menuAction"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["name", "size", "color", "strokeWidth"] }] });
523
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: HeaderComponent, isStandalone: true, selector: "lib-header", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: false, transformFunction: null }, organization: { classPropertyName: "organization", publicName: "organization", isSignal: true, isRequired: false, transformFunction: null }, navigationItems: { classPropertyName: "navigationItems", publicName: "navigationItems", isSignal: true, isRequired: false, transformFunction: null }, applications: { classPropertyName: "applications", publicName: "applications", isSignal: true, isRequired: false, transformFunction: null }, notifications: { classPropertyName: "notifications", publicName: "notifications", isSignal: true, isRequired: false, transformFunction: null }, notificationCount: { classPropertyName: "notificationCount", publicName: "notificationCount", isSignal: true, isRequired: false, transformFunction: null }, logoUrl: { classPropertyName: "logoUrl", publicName: "logoUrl", isSignal: true, isRequired: false, transformFunction: null }, appName: { classPropertyName: "appName", publicName: "appName", isSignal: true, isRequired: false, transformFunction: null }, currentAppName: { classPropertyName: "currentAppName", publicName: "currentAppName", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showNotifications: { classPropertyName: "showNotifications", publicName: "showNotifications", isSignal: true, isRequired: false, transformFunction: null }, showAppSwitcher: { classPropertyName: "showAppSwitcher", publicName: "showAppSwitcher", isSignal: true, isRequired: false, transformFunction: null }, showThemeSwitcher: { classPropertyName: "showThemeSwitcher", publicName: "showThemeSwitcher", isSignal: true, isRequired: false, transformFunction: null }, showSignIn: { classPropertyName: "showSignIn", publicName: "showSignIn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { search: "search", signIn: "signIn", applicationSelected: "applicationSelected", notificationClick: "notificationClick", markRead: "markRead", clearAll: "clearAll", menuAction: "menuAction" }, ngImport: i0, template: "<header\n class=\"header-container fixed top-0 inset-x-0 z-40 flex h-[3.75rem] items-center justify-between px-3 text-[var(--foreground)] sm:px-4 lg:px-6\">\n\n <!-- Left Side: Brand, Current App & Desktop Links -->\n <div class=\"flex min-w-0 items-center gap-3\">\n\n <a routerLink=\"/\" class=\"brand-identity\">\n <img [src]=\"logoUrl()\" [alt]=\"appName() + ' logo'\" width=\"26\" height=\"26\" class=\"brand-logo\" />\n <span class=\"brand-name\">{{ appName() }}</span>\n </a>\n\n @if (currentAppName()) {\n <div class=\"current-app-identity\" [attr.aria-label]=\"'Current app: ' + currentAppName()\">\n <span class=\"brand-separator\" aria-hidden=\"true\">|</span>\n <span class=\"current-app-name\">{{ currentAppName() }}</span>\n </div>\n }\n\n <!-- Desktop Navigation Links -->\n <nav class=\"desktop-nav ml-1 hidden items-center lg:flex\" aria-label=\"Desktop primary navigation\">\n @for (item of navigationItems(); track item.id) {\n @if (hasPermission(item)) {\n @if (item.route) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{exact: true}\" class=\"nav-link\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\">\n {{ item.label }}\n </a>\n } @else {\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" class=\"nav-link\">\n {{ item.label }}\n </a>\n }\n }\n }\n </nav>\n </div>\n\n <!-- Middle: Search Input (Desktop) -->\n @if (showSearch()) {\n <div class=\"hidden flex-1 justify-center px-4 md:flex\">\n <div class=\"header-search-shell\">\n <lib-icon name=\"search\" [size]=\"14\"\n class=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--secondary)]\" />\n\n <input type=\"search\" placeholder=\"Search Excellore...\" #searchInput\n (keyup.enter)=\"onSearch(searchInput.value); searchInput.value = ''\" class=\"header-search\" autocomplete=\"off\"\n aria-label=\"Search\" />\n </div>\n </div>\n } @else {\n <div class=\"hidden flex-1 md:block\"></div>\n }\n\n <!-- Right Side Actions -->\n <div class=\"header-actions\">\n\n <!-- Mobile Search Trigger -->\n @if (showSearch()) {\n <button type=\"button\" class=\"header-action mobile-search-action\" (click)=\"toggleMobileMenu()\"\n aria-label=\"Open mobile menu\" aria-expanded=\"isMobileMenuOpen()\" #menuToggle>\n <lib-icon name=\"search\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\n </button>\n }\n\n <!-- Theme Switcher -->\n @if (showThemeSwitcher()) {\n <button type=\"button\" class=\"header-action\" (click)=\"themeService.toggleTheme()\" aria-label=\"Toggle theme\">\n @switch (themeService.theme()) {\n @case ('light') {\n <lib-icon name=\"sun\" [size]=\"16\" class=\"text-[var(--primary)]\" />\n }\n @case ('dark') {\n <lib-icon name=\"moon\" [size]=\"16\" class=\"text-[var(--primary)]\" />\n }\n @default {\n <lib-icon name=\"monitor\" [size]=\"16\" class=\"text-[var(--secondary)]\" />\n }\n }\n </button>\n }\n\n <!-- App Switcher -->\n @if (showAppSwitcher() && applications().length > 0) {\n <lib-app-switcher [applications]=\"applications()\" (applicationSelected)=\"onApplicationSelect($event)\" />\n }\n\n <!-- Notifications -->\n @if (showNotifications()) {\n <lib-notification-bell [unreadCount]=\"unreadCount()\" [notifications]=\"notifications()\"\n (notificationClick)=\"onNotificationClick($event)\" (markRead)=\"onMarkRead($event)\" (clearAll)=\"onClearAll()\" />\n }\n\n <!-- User Menu -->\n @if (user()) {\n <div class=\"user-menu-divider\">\n <lib-user-menu [user]=\"user()\" (menuAction)=\"onMenuAction($event)\" />\n </div>\n } @else if (showSignIn()) {\n <button type=\"button\" class=\"sign-in-button\" (click)=\"signIn.emit()\">\n Sign in\n </button>\n }\n\n <!-- Mobile Menu Toggle (Hamburger) -->\n @if (hasMobileMenuContent()) {\n <button type=\"button\" class=\"header-action mobile-menu-action\" (click)=\"toggleMobileMenu()\"\n aria-label=\"Toggle navigation menu\" aria-expanded=\"isMobileMenuOpen()\" #mobileMenuToggle>\n <lib-icon [name]=\"isMobileMenuOpen() ? 'x' : 'menu'\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\n </button>\n }\n </div>\n</header>\n\n<!-- Mobile Menu Backdrop -->\n@if (isMobileMenuOpen() && hasMobileMenuContent()) {\n<div class=\"mobile-backdrop fixed inset-0 z-30 lg:hidden\" (click)=\"closeMobileMenu()\"\n (keydown.escape)=\"closeMobileMenu()\" tabindex=\"-1\" aria-hidden=\"true\"></div>\n\n<!-- Mobile Navigation Drawer -->\n<div\n class=\"mobile-drawer fixed inset-x-2 top-16 z-40 flex max-h-[calc(100dvh-4.5rem)] flex-col rounded-2xl border border-[var(--border)] bg-[var(--background)] p-3 lg:hidden\"\n role=\"dialog\" aria-modal=\"true\" aria-label=\"Mobile navigation\" (keydown.escape)=\"closeMobileMenu()\">\n <!-- Mobile Search -->\n @if (showSearch()) {\n <div class=\"relative mb-3 w-full\">\n <lib-icon name=\"search\" [size]=\"16\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--secondary)] pointer-events-none\"\n aria-hidden=\"true\" />\n\n <input type=\"search\" placeholder=\"Search Excellore...\" #mobileSearchInput\n (keyup.enter)=\"onSearch(mobileSearchInput.value); mobileSearchInput.value = ''; closeMobileMenu()\"\n class=\"header-search mobile\" autocomplete=\"off\" aria-label=\"Search\" />\n </div>\n }\n\n <!-- Mobile Nav Links -->\n <nav class=\"flex flex-col gap-1 overflow-y-auto\" role=\"navigation\">\n @for (item of navigationItems(); track item.id) {\n @if (hasPermission(item)) {\n @if (item.route) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"mobile-active\" [routerLinkActiveOptions]=\"{exact: true}\"\n (click)=\"closeMobileMenu()\" class=\"mobile-nav-link\" [attr.aria-current]=\"isActive(item) ? 'page' : null\">\n {{ item.label }}\n </a>\n } @else {\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" (click)=\"closeMobileMenu()\" class=\"mobile-nav-link\">\n {{ item.label }}\n </a>\n }\n }\n }\n </nav>\n</div>\n}\n", styles: [":host{display:block}.header-container{border-bottom:1px solid var(--border);background:var(--background);box-shadow:0 1px 2px #0f172a0a;will-change:transform}.brand-identity{display:inline-flex;min-width:0;align-items:center;gap:.5rem;border-radius:.5rem;color:var(--foreground);text-decoration:none}.brand-identity:hover{opacity:.9}.brand-logo{width:1.625rem;height:1.625rem;flex-shrink:0;-webkit-user-select:none;user-select:none}.brand-name{display:none;color:var(--foreground);font-size:.95rem;font-weight:750;letter-spacing:-.01em;white-space:nowrap}.current-app-identity{display:inline-flex;min-width:0;align-items:center;gap:.5rem}.brand-separator{color:var(--border);font-size:1rem;font-weight:500}.current-app-name{max-width:11rem;overflow:hidden;color:var(--foreground);font-size:.875rem;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.desktop-nav{gap:1.25rem;padding:0;background:transparent}.nav-link{position:relative;display:inline-flex;align-items:center;min-height:2rem;color:var(--secondary);padding:0;font-size:.78rem;font-weight:600;line-height:1;text-decoration:none;transition:color .15s ease,opacity .15s ease}.nav-link:hover{color:var(--foreground)}.nav-link.active{color:var(--foreground)!important;font-weight:700}.nav-link.active:after{content:\"\";position:absolute;right:0;bottom:-.85rem;left:0;height:.125rem;background-color:var(--primary);border-radius:9999px}.header-search-shell{position:relative;width:min(100%,20rem)}.header-search{width:100%;height:2.25rem;border:1px solid var(--border);border-radius:.5rem;outline:0;color:var(--foreground);background:var(--background);padding:0 .9rem 0 2.25rem;font-size:.78rem;transition:border-color .15s ease,box-shadow .15s ease}.header-search::placeholder{color:var(--secondary)}.header-search:focus{border-color:var(--primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--primary) 16%,transparent)}.header-search.mobile{width:100%;height:2.6rem;border-radius:.625rem;font-size:.875rem}.header-search.mobile:focus{width:100%}.header-actions{display:flex;align-items:center;gap:.25rem;background:transparent}.header-action{display:grid;width:2.15rem;height:2.15rem;place-items:center;border:1px solid transparent;border-radius:.5rem;color:var(--foreground);background:transparent;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.header-action:hover{border-color:var(--border);background:color-mix(in srgb,var(--border) 35%,transparent)}.user-menu-divider{display:flex;height:1.75rem;align-items:center;margin-left:.2rem;border-left:1px solid var(--border);padding-left:.35rem}.sign-in-button{height:2.15rem;border:1px solid var(--primary);border-radius:.5rem;color:#fff;background:var(--primary);padding:0 .85rem;font-size:.825rem;font-weight:700;cursor:pointer;transition:opacity .15s ease,box-shadow .15s ease}.sign-in-button:hover{opacity:.92;box-shadow:0 4px 10px color-mix(in srgb,var(--primary) 18%,transparent)}.sign-in-button:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.mobile-backdrop{background:#0f172a29}.mobile-drawer{box-shadow:0 12px 30px #0f172a24;animation:slideDown .2s cubic-bezier(.16,1,.3,1)}.mobile-nav-link{display:flex;align-items:center;min-height:2.75rem;border-radius:.625rem;color:var(--secondary);padding:0 .85rem;font-size:.925rem;font-weight:700;text-decoration:none;transition:background-color .15s ease,color .15s ease,transform .15s ease}.mobile-nav-link:hover{color:var(--foreground);background:color-mix(in srgb,var(--border) 35%,transparent)}.mobile-active{color:var(--foreground)!important;background:color-mix(in srgb,var(--primary) 10%,transparent);font-weight:700}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}:focus-visible{outline:2px solid var(--primary);outline-offset:2px}:focus:not(:focus-visible){outline:none}lucide-icon{display:inline-flex;vertical-align:middle}*{transition-property:background-color,border-color,color;transition-duration:.15s;transition-timing-function:ease-in-out}@media(min-width:640px){.brand-name{display:inline-block}.header-actions{gap:.25rem}.user-menu-divider{padding-left:.5rem}}@media(min-width:768px){.mobile-search-action{display:none}}@media(min-width:1024px){.mobile-menu-action{display:none}}@media(max-width:420px){.current-app-name{max-width:8rem}.header-actions{gap:0;padding:0;background:transparent}.header-action{width:2rem;height:2rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: AppSwitcherComponent, selector: "lib-app-switcher", inputs: ["applications"], outputs: ["applicationSelected"] }, { kind: "component", type: NotificationBellComponent, selector: "lib-notification-bell", inputs: ["unreadCount", "notifications"], outputs: ["notificationClick", "markRead", "clearAll"] }, { kind: "component", type: UserMenuComponent, selector: "lib-user-menu", inputs: ["user"], outputs: ["menuAction"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["name", "size", "color", "strokeWidth"] }] });
496
524
  }
497
525
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: HeaderComponent, decorators: [{
498
526
  type: Component,
@@ -504,8 +532,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
504
532
  NotificationBellComponent,
505
533
  UserMenuComponent,
506
534
  IconComponent,
507
- ], template: "<header\n class=\"header-container fixed top-0 inset-x-0 z-40 flex h-14 items-center justify-between border-b border-[var(--border)] bg-[var(--background)]/80 px-4 text-[var(--foreground)] backdrop-blur-md\">\n\n <!-- Left Side: Organization & Desktop Links -->\n <div class=\"flex items-center gap-3.5\">\n\n <!-- Excellore brand logo/title hidden while the organization is the primary header identity.\n <a routerLink=\"/\"\n class=\"flex items-center gap-2 text-sm font-bold tracking-tight text-[var(--foreground)] no-underline hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-[var(--primary)] rounded\">\n <img [src]=\"logoUrl()\" [alt]=\"appName() + ' logo'\" width=\"26\" height=\"26\" class=\"h-6.5 w-6.5 select-none\" />\n <span class=\"hidden sm:inline-block\">{{ appName() }}</span>\n </a>\n -->\n\n <!-- Organization Identity -->\n @if (organization(); as organization) {\n <div class=\"h-8 flex items-center\">\n <div class=\"flex items-center gap-2.5\" [attr.aria-label]=\"'Organization: ' + organization.name\">\n @if (organization.logoUrl) {\n <img [src]=\"organization.logoUrl\" [alt]=\"organization.name + ' logo'\"\n class=\"h-8 w-8 rounded-md object-contain bg-white\" />\n } @else {\n <span\n class=\"grid h-8 w-8 place-items-center rounded-md bg-[var(--primary)] text-[11px] font-bold text-white\"\n aria-hidden=\"true\">\n {{ getOrganizationInitials(organization) }}\n </span>\n }\n <span class=\"hidden max-w-[180px] truncate text-sm font-semibold text-[var(--foreground)] sm:inline\">\n {{ organization.name }}\n </span>\n </div>\n </div>\n }\n\n <!-- Desktop Navigation Links -->\n <nav class=\"ml-2 hidden lg:flex items-center gap-5.5\" aria-label=\"Desktop primary navigation\">\n @for (item of navigationItems(); track item.id) {\n @if (hasPermission(item)) {\n @if (item.route) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{exact: true}\"\n class=\"nav-link text-xs font-medium text-[var(--secondary)] transition hover:text-[var(--foreground)] no-underline py-1 relative focus:outline-none focus:ring-2 focus:ring-[var(--primary)] rounded\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\">\n {{ item.label }}\n </a>\n } @else {\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\"\n class=\"nav-link text-xs font-medium text-[var(--secondary)] transition hover:text-[var(--foreground)] no-underline py-1 relative focus:outline-none focus:ring-2 focus:ring-[var(--primary)] rounded\">\n {{ item.label }}\n </a>\n }\n }\n }\n </nav>\n </div>\n\n <!-- Middle: Search Input (Desktop) -->\n <div class=\"hidden md:flex flex-1 max-w-[260px] mx-4\">\n <div class=\"relative w-full\">\n <!-- <lucide-icon [name]=\"searchIcon\"\n class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[var(--secondary)] pointer-events-none\"\n aria-hidden=\"true\" /> -->\n <lib-icon name=\"search\" [size]=\"14\"\n class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[var(--secondary)]\" />\n\n <input type=\"search\" placeholder=\"Search Excellore...\" #searchInput\n (keyup.enter)=\"onSearch(searchInput.value); searchInput.value = ''\"\n class=\"w-full h-8 pl-8 pr-3 text-xs bg-[rgba(var(--foreground),0.02)] border border-[var(--border)] rounded-md focus:outline-none focus:bg-[var(--background)] focus:ring-2 focus:ring-[var(--primary)] text-[var(--foreground)] transition-all placeholder:text-[var(--secondary)]/60\"\n autocomplete=\"off\" aria-label=\"Search\" />\n </div>\n </div>\n\n <!-- Right Side Actions -->\n <div class=\"flex items-center gap-1 sm:gap-1.5\">\n\n <!-- Mobile Search Trigger -->\n <button type=\"button\"\n class=\"grid md:hidden h-8.5 w-8.5 place-items-center rounded-md border border-transparent hover:bg-[rgba(var(--foreground),0.03)] text-[var(--foreground)] cursor-pointer focus:outline-none focus:ring-2 focus:ring-[var(--primary)]\"\n (click)=\"toggleMobileMenu()\" aria-label=\"Open mobile menu\" aria-expanded=\"isMobileMenuOpen()\" #menuToggle>\n <!-- <lucide-icon [name]=\"searchIcon\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" /> -->\n <lib-icon name=\"search\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\n </button>\n\n <!-- Theme Switcher -->\n <button type=\"button\"\n class=\"grid h-8.5 w-8.5 place-items-center rounded-md border border-transparent text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"themeService.toggleTheme()\" aria-label=\"Toggle theme\">\n @switch (themeService.theme()) {\n @case ('light') {\n <!-- <lucide-icon [name]=\"sunIcon\" class=\"h-4 w-4 text-[var(--primary)]\" aria-hidden=\"true\" /> -->\n <lib-icon name=\"sun\" [size]=\"16\" class=\"text-[var(--primary)]\" />\n }\n @case ('dark') {\n <!-- <lucide-icon [name]=\"moonIcon\" class=\"h-4 w-4 text-[var(--primary)]\" aria-hidden=\"true\" /> -->\n <lib-icon name=\"moon\" [size]=\"16\" class=\"text-[var(--primary)]\" />\n }\n @default {\n <!-- <lucide-icon [name]=\"monitorIcon\" class=\"h-4 w-4 text-[var(--secondary)]\" aria-hidden=\"true\" /> -->\n <lib-icon name=\"monitor\" [size]=\"16\" class=\"text-[var(--secondary)]\" />\n }\n }\n </button>\n\n <!-- App Switcher -->\n @if (applications().length > 0) {\n <lib-app-switcher [applications]=\"applications()\" (applicationSelected)=\"onApplicationSelect($event)\" />\n }\n\n <!-- Notifications -->\n <lib-notification-bell [unreadCount]=\"unreadCount()\" [notifications]=\"notifications()\"\n (notificationClick)=\"onNotificationClick($event)\" (markRead)=\"onMarkRead($event)\" (clearAll)=\"onClearAll()\" />\n\n <!-- User Menu -->\n @if (user()) {\n <div class=\"h-6 border-l border-[var(--border)] pl-1.5 sm:pl-2 flex items-center ml-0.5\">\n <lib-user-menu [user]=\"user()\" (menuAction)=\"onMenuAction($event)\" />\n </div>\n }\n\n <!-- Mobile Menu Toggle (Hamburger) -->\n <button type=\"button\"\n class=\"grid lg:hidden h-8.5 w-8.5 place-items-center rounded-md border border-transparent text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] cursor-pointer\"\n (click)=\"toggleMobileMenu()\" aria-label=\"Toggle navigation menu\" aria-expanded=\"isMobileMenuOpen()\"\n #mobileMenuToggle>\n <!-- <lucide-icon [name]=\"isMobileMenuOpen() ? xIcon : menuIcon\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" /> -->\n <lib-icon [name]=\"isMobileMenuOpen() ? 'x' : 'menu'\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\n </button>\n </div>\n</header>\n\n<!-- Mobile Menu Backdrop -->\n@if (isMobileMenuOpen()) {\n<div class=\"fixed inset-0 z-30 bg-black/20 lg:hidden\" (click)=\"closeMobileMenu()\" (keydown.escape)=\"closeMobileMenu()\"\n tabindex=\"-1\" aria-hidden=\"true\"></div>\n\n<!-- Mobile Navigation Drawer -->\n<div\n class=\"mobile-drawer fixed top-14 inset-x-0 bottom-0 z-40 flex flex-col bg-[var(--background)] border-t border-[var(--border)] p-4 lg:hidden\"\n role=\"dialog\" aria-modal=\"true\" aria-label=\"Mobile navigation\" (keydown.escape)=\"closeMobileMenu()\">\n <!-- Mobile Search -->\n <div class=\"relative w-full mb-4\">\n <!-- <lucide-icon [name]=\"searchIcon\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--secondary)] pointer-events-none\"\n aria-hidden=\"true\" /> -->\n\n <lib-icon name=\"search\" [size]=\"16\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--secondary)] pointer-events-none\"\n aria-hidden=\"true\" />\n\n <input type=\"search\" placeholder=\"Search Excellore...\" #mobileSearchInput\n (keyup.enter)=\"onSearch(mobileSearchInput.value); mobileSearchInput.value = ''; closeMobileMenu()\"\n class=\"w-full h-10 pl-9 pr-3 text-sm bg-[rgba(var(--foreground),0.02)] border border-[var(--border)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--primary)] text-[var(--foreground)] placeholder:text-[var(--secondary)]/60\"\n autocomplete=\"off\" aria-label=\"Search\" />\n </div>\n\n <!-- Mobile Nav Links -->\n <nav class=\"flex flex-col gap-1 overflow-y-auto max-h-[70vh]\" role=\"navigation\">\n @for (item of navigationItems(); track item.id) {\n @if (hasPermission(item)) {\n @if (item.route) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"mobile-active\" [routerLinkActiveOptions]=\"{exact: true}\"\n (click)=\"closeMobileMenu()\"\n class=\"flex items-center px-3 py-2.5 rounded-lg text-sm font-semibold text-[var(--secondary)] hover:text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] no-underline focus:outline-none focus:ring-2 focus:ring-[var(--primary)]\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\">\n {{ item.label }}\n </a>\n } @else {\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" (click)=\"closeMobileMenu()\"\n class=\"flex items-center px-3 py-2.5 rounded-lg text-sm font-semibold text-[var(--secondary)] hover:text-[var(--foreground)] hover:bg-[rgba(var(--foreground),0.03)] no-underline focus:outline-none focus:ring-2 focus:ring-[var(--primary)]\">\n {{ item.label }}\n </a>\n }\n }\n }\n </nav>\n</div>\n}\n", styles: [":host{display:block}.header-container{box-shadow:0 1px 2px #00000008;will-change:transform}.nav-link{transition:color .15s ease-in-out}.nav-link.active{color:var(--foreground)!important;font-weight:600}.nav-link.active:after{content:\"\";position:absolute;bottom:-14px;left:0;right:0;height:2px;background-color:rgb(var(--primary));border-radius:9999px}.mobile-drawer{box-shadow:0 10px 15px -3px #0000000d,0 4px 6px -2px #00000008;animation:slideDown .2s cubic-bezier(.16,1,.3,1)}.mobile-active{color:var(--foreground)!important;background-color:rgba(var(--foreground),.03);font-weight:700}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}:focus-visible{outline:2px solid rgb(var(--primary));outline-offset:2px}:focus:not(:focus-visible){outline:none}@supports not (backdrop-filter: blur(12px)){.header-container{background-color:rgb(var(--background))}}lucide-icon{display:inline-flex;vertical-align:middle}*{transition-property:background-color,border-color,color;transition-duration:.15s;transition-timing-function:ease-in-out}\n"] }]
508
- }], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }], permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", required: false }] }], organization: [{ type: i0.Input, args: [{ isSignal: true, alias: "organization", required: false }] }], navigationItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigationItems", required: false }] }], applications: [{ type: i0.Input, args: [{ isSignal: true, alias: "applications", required: false }] }], notifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "notifications", required: false }] }], notificationCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "notificationCount", required: false }] }], logoUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoUrl", required: false }] }], appName: [{ type: i0.Input, args: [{ isSignal: true, alias: "appName", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], applicationSelected: [{ type: i0.Output, args: ["applicationSelected"] }], notificationClick: [{ type: i0.Output, args: ["notificationClick"] }], markRead: [{ type: i0.Output, args: ["markRead"] }], clearAll: [{ type: i0.Output, args: ["clearAll"] }], menuAction: [{ type: i0.Output, args: ["menuAction"] }] } });
535
+ ], template: "<header\n class=\"header-container fixed top-0 inset-x-0 z-40 flex h-[3.75rem] items-center justify-between px-3 text-[var(--foreground)] sm:px-4 lg:px-6\">\n\n <!-- Left Side: Brand, Current App & Desktop Links -->\n <div class=\"flex min-w-0 items-center gap-3\">\n\n <a routerLink=\"/\" class=\"brand-identity\">\n <img [src]=\"logoUrl()\" [alt]=\"appName() + ' logo'\" width=\"26\" height=\"26\" class=\"brand-logo\" />\n <span class=\"brand-name\">{{ appName() }}</span>\n </a>\n\n @if (currentAppName()) {\n <div class=\"current-app-identity\" [attr.aria-label]=\"'Current app: ' + currentAppName()\">\n <span class=\"brand-separator\" aria-hidden=\"true\">|</span>\n <span class=\"current-app-name\">{{ currentAppName() }}</span>\n </div>\n }\n\n <!-- Desktop Navigation Links -->\n <nav class=\"desktop-nav ml-1 hidden items-center lg:flex\" aria-label=\"Desktop primary navigation\">\n @for (item of navigationItems(); track item.id) {\n @if (hasPermission(item)) {\n @if (item.route) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{exact: true}\" class=\"nav-link\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\">\n {{ item.label }}\n </a>\n } @else {\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" class=\"nav-link\">\n {{ item.label }}\n </a>\n }\n }\n }\n </nav>\n </div>\n\n <!-- Middle: Search Input (Desktop) -->\n @if (showSearch()) {\n <div class=\"hidden flex-1 justify-center px-4 md:flex\">\n <div class=\"header-search-shell\">\n <lib-icon name=\"search\" [size]=\"14\"\n class=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--secondary)]\" />\n\n <input type=\"search\" placeholder=\"Search Excellore...\" #searchInput\n (keyup.enter)=\"onSearch(searchInput.value); searchInput.value = ''\" class=\"header-search\" autocomplete=\"off\"\n aria-label=\"Search\" />\n </div>\n </div>\n } @else {\n <div class=\"hidden flex-1 md:block\"></div>\n }\n\n <!-- Right Side Actions -->\n <div class=\"header-actions\">\n\n <!-- Mobile Search Trigger -->\n @if (showSearch()) {\n <button type=\"button\" class=\"header-action mobile-search-action\" (click)=\"toggleMobileMenu()\"\n aria-label=\"Open mobile menu\" aria-expanded=\"isMobileMenuOpen()\" #menuToggle>\n <lib-icon name=\"search\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\n </button>\n }\n\n <!-- Theme Switcher -->\n @if (showThemeSwitcher()) {\n <button type=\"button\" class=\"header-action\" (click)=\"themeService.toggleTheme()\" aria-label=\"Toggle theme\">\n @switch (themeService.theme()) {\n @case ('light') {\n <lib-icon name=\"sun\" [size]=\"16\" class=\"text-[var(--primary)]\" />\n }\n @case ('dark') {\n <lib-icon name=\"moon\" [size]=\"16\" class=\"text-[var(--primary)]\" />\n }\n @default {\n <lib-icon name=\"monitor\" [size]=\"16\" class=\"text-[var(--secondary)]\" />\n }\n }\n </button>\n }\n\n <!-- App Switcher -->\n @if (showAppSwitcher() && applications().length > 0) {\n <lib-app-switcher [applications]=\"applications()\" (applicationSelected)=\"onApplicationSelect($event)\" />\n }\n\n <!-- Notifications -->\n @if (showNotifications()) {\n <lib-notification-bell [unreadCount]=\"unreadCount()\" [notifications]=\"notifications()\"\n (notificationClick)=\"onNotificationClick($event)\" (markRead)=\"onMarkRead($event)\" (clearAll)=\"onClearAll()\" />\n }\n\n <!-- User Menu -->\n @if (user()) {\n <div class=\"user-menu-divider\">\n <lib-user-menu [user]=\"user()\" (menuAction)=\"onMenuAction($event)\" />\n </div>\n } @else if (showSignIn()) {\n <button type=\"button\" class=\"sign-in-button\" (click)=\"signIn.emit()\">\n Sign in\n </button>\n }\n\n <!-- Mobile Menu Toggle (Hamburger) -->\n @if (hasMobileMenuContent()) {\n <button type=\"button\" class=\"header-action mobile-menu-action\" (click)=\"toggleMobileMenu()\"\n aria-label=\"Toggle navigation menu\" aria-expanded=\"isMobileMenuOpen()\" #mobileMenuToggle>\n <lib-icon [name]=\"isMobileMenuOpen() ? 'x' : 'menu'\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\n </button>\n }\n </div>\n</header>\n\n<!-- Mobile Menu Backdrop -->\n@if (isMobileMenuOpen() && hasMobileMenuContent()) {\n<div class=\"mobile-backdrop fixed inset-0 z-30 lg:hidden\" (click)=\"closeMobileMenu()\"\n (keydown.escape)=\"closeMobileMenu()\" tabindex=\"-1\" aria-hidden=\"true\"></div>\n\n<!-- Mobile Navigation Drawer -->\n<div\n class=\"mobile-drawer fixed inset-x-2 top-16 z-40 flex max-h-[calc(100dvh-4.5rem)] flex-col rounded-2xl border border-[var(--border)] bg-[var(--background)] p-3 lg:hidden\"\n role=\"dialog\" aria-modal=\"true\" aria-label=\"Mobile navigation\" (keydown.escape)=\"closeMobileMenu()\">\n <!-- Mobile Search -->\n @if (showSearch()) {\n <div class=\"relative mb-3 w-full\">\n <lib-icon name=\"search\" [size]=\"16\"\n class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--secondary)] pointer-events-none\"\n aria-hidden=\"true\" />\n\n <input type=\"search\" placeholder=\"Search Excellore...\" #mobileSearchInput\n (keyup.enter)=\"onSearch(mobileSearchInput.value); mobileSearchInput.value = ''; closeMobileMenu()\"\n class=\"header-search mobile\" autocomplete=\"off\" aria-label=\"Search\" />\n </div>\n }\n\n <!-- Mobile Nav Links -->\n <nav class=\"flex flex-col gap-1 overflow-y-auto\" role=\"navigation\">\n @for (item of navigationItems(); track item.id) {\n @if (hasPermission(item)) {\n @if (item.route) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"mobile-active\" [routerLinkActiveOptions]=\"{exact: true}\"\n (click)=\"closeMobileMenu()\" class=\"mobile-nav-link\" [attr.aria-current]=\"isActive(item) ? 'page' : null\">\n {{ item.label }}\n </a>\n } @else {\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" (click)=\"closeMobileMenu()\" class=\"mobile-nav-link\">\n {{ item.label }}\n </a>\n }\n }\n }\n </nav>\n</div>\n}\n", styles: [":host{display:block}.header-container{border-bottom:1px solid var(--border);background:var(--background);box-shadow:0 1px 2px #0f172a0a;will-change:transform}.brand-identity{display:inline-flex;min-width:0;align-items:center;gap:.5rem;border-radius:.5rem;color:var(--foreground);text-decoration:none}.brand-identity:hover{opacity:.9}.brand-logo{width:1.625rem;height:1.625rem;flex-shrink:0;-webkit-user-select:none;user-select:none}.brand-name{display:none;color:var(--foreground);font-size:.95rem;font-weight:750;letter-spacing:-.01em;white-space:nowrap}.current-app-identity{display:inline-flex;min-width:0;align-items:center;gap:.5rem}.brand-separator{color:var(--border);font-size:1rem;font-weight:500}.current-app-name{max-width:11rem;overflow:hidden;color:var(--foreground);font-size:.875rem;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.desktop-nav{gap:1.25rem;padding:0;background:transparent}.nav-link{position:relative;display:inline-flex;align-items:center;min-height:2rem;color:var(--secondary);padding:0;font-size:.78rem;font-weight:600;line-height:1;text-decoration:none;transition:color .15s ease,opacity .15s ease}.nav-link:hover{color:var(--foreground)}.nav-link.active{color:var(--foreground)!important;font-weight:700}.nav-link.active:after{content:\"\";position:absolute;right:0;bottom:-.85rem;left:0;height:.125rem;background-color:var(--primary);border-radius:9999px}.header-search-shell{position:relative;width:min(100%,20rem)}.header-search{width:100%;height:2.25rem;border:1px solid var(--border);border-radius:.5rem;outline:0;color:var(--foreground);background:var(--background);padding:0 .9rem 0 2.25rem;font-size:.78rem;transition:border-color .15s ease,box-shadow .15s ease}.header-search::placeholder{color:var(--secondary)}.header-search:focus{border-color:var(--primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--primary) 16%,transparent)}.header-search.mobile{width:100%;height:2.6rem;border-radius:.625rem;font-size:.875rem}.header-search.mobile:focus{width:100%}.header-actions{display:flex;align-items:center;gap:.25rem;background:transparent}.header-action{display:grid;width:2.15rem;height:2.15rem;place-items:center;border:1px solid transparent;border-radius:.5rem;color:var(--foreground);background:transparent;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.header-action:hover{border-color:var(--border);background:color-mix(in srgb,var(--border) 35%,transparent)}.user-menu-divider{display:flex;height:1.75rem;align-items:center;margin-left:.2rem;border-left:1px solid var(--border);padding-left:.35rem}.sign-in-button{height:2.15rem;border:1px solid var(--primary);border-radius:.5rem;color:#fff;background:var(--primary);padding:0 .85rem;font-size:.825rem;font-weight:700;cursor:pointer;transition:opacity .15s ease,box-shadow .15s ease}.sign-in-button:hover{opacity:.92;box-shadow:0 4px 10px color-mix(in srgb,var(--primary) 18%,transparent)}.sign-in-button:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.mobile-backdrop{background:#0f172a29}.mobile-drawer{box-shadow:0 12px 30px #0f172a24;animation:slideDown .2s cubic-bezier(.16,1,.3,1)}.mobile-nav-link{display:flex;align-items:center;min-height:2.75rem;border-radius:.625rem;color:var(--secondary);padding:0 .85rem;font-size:.925rem;font-weight:700;text-decoration:none;transition:background-color .15s ease,color .15s ease,transform .15s ease}.mobile-nav-link:hover{color:var(--foreground);background:color-mix(in srgb,var(--border) 35%,transparent)}.mobile-active{color:var(--foreground)!important;background:color-mix(in srgb,var(--primary) 10%,transparent);font-weight:700}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}:focus-visible{outline:2px solid var(--primary);outline-offset:2px}:focus:not(:focus-visible){outline:none}lucide-icon{display:inline-flex;vertical-align:middle}*{transition-property:background-color,border-color,color;transition-duration:.15s;transition-timing-function:ease-in-out}@media(min-width:640px){.brand-name{display:inline-block}.header-actions{gap:.25rem}.user-menu-divider{padding-left:.5rem}}@media(min-width:768px){.mobile-search-action{display:none}}@media(min-width:1024px){.mobile-menu-action{display:none}}@media(max-width:420px){.current-app-name{max-width:8rem}.header-actions{gap:0;padding:0;background:transparent}.header-action{width:2rem;height:2rem}}\n"] }]
536
+ }], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }], permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", required: false }] }], organization: [{ type: i0.Input, args: [{ isSignal: true, alias: "organization", required: false }] }], navigationItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigationItems", required: false }] }], applications: [{ type: i0.Input, args: [{ isSignal: true, alias: "applications", required: false }] }], notifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "notifications", required: false }] }], notificationCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "notificationCount", required: false }] }], logoUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoUrl", required: false }] }], appName: [{ type: i0.Input, args: [{ isSignal: true, alias: "appName", required: false }] }], currentAppName: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentAppName", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], showNotifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNotifications", required: false }] }], showAppSwitcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAppSwitcher", required: false }] }], showThemeSwitcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSwitcher", required: false }] }], showSignIn: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSignIn", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], signIn: [{ type: i0.Output, args: ["signIn"] }], applicationSelected: [{ type: i0.Output, args: ["applicationSelected"] }], notificationClick: [{ type: i0.Output, args: ["notificationClick"] }], markRead: [{ type: i0.Output, args: ["markRead"] }], clearAll: [{ type: i0.Output, args: ["clearAll"] }], menuAction: [{ type: i0.Output, args: ["menuAction"] }] } });
509
537
 
510
538
  /*
511
539
  * Public API Surface of ui