@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.
- package/README.md +64 -64
- package/fesm2022/excellore-ui.mjs +105 -77
- package/fesm2022/excellore-ui.mjs.map +1 -1
- package/package.json +2 -1
- package/types/excellore-ui.d.ts +9 -18
|
@@ -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
|
|
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=\"
|
|
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,
|
|
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 <
|
|
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,
|
|
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 <
|
|
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,
|
|
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=\"
|
|
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,
|
|
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
|
|
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,
|
|
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.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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: [
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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/
|
|
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
|