@excellore/ui 0.0.8 → 0.0.10
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/fesm2022/excellore-ui.mjs +162 -112
- package/fesm2022/excellore-ui.mjs.map +1 -1
- package/package.json +3 -3
- package/types/excellore-ui.d.ts +27 -81
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, PLATFORM_ID, signal, effect, Injectable, input, output, HostListener, Component, computed } from '@angular/core';
|
|
3
|
-
import { isPlatformBrowser, CommonModule } from '@angular/common';
|
|
4
|
-
import {
|
|
5
|
-
import { LucideGrid,
|
|
3
|
+
import { isPlatformBrowser, CommonModule, NgClass, NgTemplateOutlet } from '@angular/common';
|
|
4
|
+
import { RouterLink, RouterLinkActive, Router } from '@angular/router';
|
|
5
|
+
import { LucideGrid, LucideBellOff, LucideCheckCheck, LucideTrash2, LucideBell, LucideChevronDown, LucideCreditCard, LucideLogOut, LucideSettings, LucideUser, LucideBuilding2, LucideMenu, LucideMonitor, LucideMoon, LucideSearch, LucideSun, LucideX } from '@lucide/angular';
|
|
6
6
|
|
|
7
7
|
const THEME_STORAGE_KEY = 'excellore-theme';
|
|
8
8
|
class ThemeService {
|
|
@@ -150,47 +150,76 @@ class AppSwitcherComponent {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AppSwitcherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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\">\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=\"app-switcher-trigger-icon\"></svg>\n </button>\n\n @if (isOpen()) {\n <div class=\"app-switcher-dropdown\">\n <div class=\"app-switcher-header\">\n <div>\n <h3>Excellore Apps</h3>\n <p>Switch between available workspaces</p>\n </div>\n </div>\n \n @if (applications().length === 0) {\n <div class=\"app-switcher-empty\">\n No other apps available\n </div>\n } @else {\n <div class=\"app-switcher-list\">\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\"\n >\n <div class=\"app-switcher-app-mark\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"app-switcher-app-content\">\n <div class=\"app-switcher-app-name\" [title]=\"app.name\">{{ app.name }}</div>\n <div class=\"app-switcher-app-url\">{{ app.url }}</div>\n </div>\n <span class=\"app-switcher-open-pill\">\n Open\n </span>\n </a>\n } @else {\n <div class=\"app-switcher-item unavailable\">\n <div class=\"app-switcher-app-mark unavailable\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"app-switcher-app-content\">\n <div class=\"app-switcher-app-name\">{{ app.name }}</div>\n <div class=\"app-switcher-app-url\">{{ app.url }}</div>\n </div>\n <span class=\"app-switcher-soon-pill\">\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-wrapper{position:relative;display:inline-block;text-align:left}.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-trigger-icon{width:1.125rem;height:1.125rem}.app-switcher-dropdown{position:fixed;z-index:50;top:3.75rem;right:.5rem;left:.5rem;max-height:calc(100dvh - 4.5rem);overflow:hidden;border:1px solid var(--border);border-radius:.875rem;background:var(--background);padding:.625rem;box-shadow:0 12px 30px #0f172a24;animation:switcherEnter .15s cubic-bezier(.16,1,.3,1)}.app-switcher-header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);margin-bottom:.375rem;padding:0 .25rem .625rem}.app-switcher-header h3{margin:0;color:var(--secondary);font-size:.65rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.app-switcher-header p{margin:.125rem 0 0;color:var(--secondary);font-size:.72rem;line-height:1.35}.app-switcher-empty{padding:1.5rem .5rem;color:var(--secondary);font-size:.8rem;text-align:center}.app-switcher-list{max-height:calc(100dvh - 10rem);overflow-y:auto;padding:.25rem 0}.app-switcher-item{display:flex;align-items:center;gap:.75rem;border:1px solid transparent;border-radius:.75rem;padding:.65rem;text-decoration:none;transition:background-color .15s ease,border-color .15s ease}.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-link:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.app-switcher-item.unavailable{opacity:.55;-webkit-user-select:none;user-select:none}.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-content{min-width:0;flex:1 1 auto}.app-switcher-app-name{overflow:hidden;color:var(--foreground);font-size:.875rem;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.app-switcher-app-url{overflow:hidden;margin-top:.125rem;color:var(--secondary);font-size:.72rem;text-overflow:ellipsis;white-space:nowrap}.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{flex-shrink:0;border-radius:999px;color:var(--primary);background:color-mix(in srgb,var(--primary) 9%,transparent);padding:.15rem .5rem;font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;opacity:0;transition:opacity .15s ease}.app-switcher-link:hover .app-switcher-open-pill{opacity:1}.app-switcher-soon-pill{flex-shrink:0;border:1px solid var(--border);border-radius:999px;color:var(--secondary);padding:.15rem .5rem;font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.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)}}@media(min-width:640px){.app-switcher-dropdown{position:absolute;top:calc(100% + .5rem);right:0;left:auto;width:22.5rem;max-width:calc(100vw - 1rem);max-height:calc(100dvh - 5rem);transform-origin:top right}.app-switcher-list{max-height:26.25rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideGrid, selector: "svg[lucideGrid3x3], svg[lucideGrid], svg[lucideGrid3X3]" }] });
|
|
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\">\r\n <button\r\n type=\"button\"\r\n class=\"app-switcher-trigger\"\r\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=\"app-switcher-trigger-icon\"></svg>\r\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"app-switcher-dropdown\">\r\n <div class=\"app-switcher-header\">\r\n <div>\r\n <h3>Excellore Apps</h3>\r\n <p>Switch between available workspaces</p>\r\n </div>\r\n </div>\r\n \r\n @if (applications().length === 0) {\r\n <div class=\"app-switcher-empty\">\r\n No other apps available\r\n </div>\r\n } @else {\r\n <div class=\"app-switcher-list\">\r\n @for (app of applications(); track app.id) {\r\n @if (app.available !== false) {\r\n <a\r\n [href]=\"app.url\"\r\n (click)=\"onSelect(app)\"\r\n class=\"app-switcher-item app-switcher-link\"\r\n >\r\n <div class=\"app-switcher-app-mark\">\r\n {{ app.initials || getInitials(app.name) }}\r\n </div>\r\n <div class=\"app-switcher-app-content\">\r\n <div class=\"app-switcher-app-name\" [title]=\"app.name\">{{ app.name }}</div>\r\n <div class=\"app-switcher-app-url\">{{ app.url }}</div>\r\n </div>\r\n <span class=\"app-switcher-open-pill\">\r\n Open\r\n </span>\r\n </a>\r\n } @else {\r\n <div class=\"app-switcher-item unavailable\">\r\n <div class=\"app-switcher-app-mark unavailable\">\r\n {{ app.initials || getInitials(app.name) }}\r\n </div>\r\n <div class=\"app-switcher-app-content\">\r\n <div class=\"app-switcher-app-name\">{{ app.name }}</div>\r\n <div class=\"app-switcher-app-url\">{{ app.url }}</div>\r\n </div>\r\n <span class=\"app-switcher-soon-pill\">\r\n Soon\r\n </span>\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.app-switcher-wrapper{position:relative;display:inline-block;text-align:left}.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-trigger-icon{width:1.125rem;height:1.125rem}.app-switcher-dropdown{position:fixed;z-index:50;top:3.75rem;right:.5rem;left:.5rem;max-height:calc(100dvh - 4.5rem);overflow:hidden;border:1px solid var(--border);border-radius:.875rem;background:var(--background);padding:.625rem;box-shadow:0 12px 30px #0f172a24;animation:switcherEnter .15s cubic-bezier(.16,1,.3,1)}.app-switcher-header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);margin-bottom:.375rem;padding:0 .25rem .625rem}.app-switcher-header h3{margin:0;color:var(--secondary);font-size:.65rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.app-switcher-header p{margin:.125rem 0 0;color:var(--secondary);font-size:.72rem;line-height:1.35}.app-switcher-empty{padding:1.5rem .5rem;color:var(--secondary);font-size:.8rem;text-align:center}.app-switcher-list{max-height:calc(100dvh - 10rem);overflow-y:auto;padding:.25rem 0}.app-switcher-item{display:flex;align-items:center;gap:.75rem;border:1px solid transparent;border-radius:.75rem;padding:.65rem;text-decoration:none;transition:background-color .15s ease,border-color .15s ease}.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-link:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.app-switcher-item.unavailable{opacity:.55;-webkit-user-select:none;user-select:none}.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-content{min-width:0;flex:1 1 auto}.app-switcher-app-name{overflow:hidden;color:var(--foreground);font-size:.875rem;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.app-switcher-app-url{overflow:hidden;margin-top:.125rem;color:var(--secondary);font-size:.72rem;text-overflow:ellipsis;white-space:nowrap}.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{flex-shrink:0;border-radius:999px;color:var(--primary);background:color-mix(in srgb,var(--primary) 9%,transparent);padding:.15rem .5rem;font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;opacity:0;transition:opacity .15s ease}.app-switcher-link:hover .app-switcher-open-pill{opacity:1}.app-switcher-soon-pill{flex-shrink:0;border:1px solid var(--border);border-radius:999px;color:var(--secondary);padding:.15rem .5rem;font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.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)}}@media(min-width:640px){.app-switcher-dropdown{position:absolute;top:calc(100% + .5rem);right:0;left:auto;width:22.5rem;max-width:calc(100vw - 1rem);max-height:calc(100dvh - 5rem);transform-origin:top right}.app-switcher-list{max-height:26.25rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideGrid, selector: "svg[lucideGrid3x3], svg[lucideGrid], svg[lucideGrid3X3]" }] });
|
|
154
154
|
}
|
|
155
155
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AppSwitcherComponent, decorators: [{
|
|
156
156
|
type: Component,
|
|
157
|
-
args: [{ selector: 'lib-app-switcher', standalone: true, imports: [CommonModule, LucideGrid], template: "<div class=\"app-switcher-wrapper\">\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=\"app-switcher-trigger-icon\"></svg>\n </button>\n\n @if (isOpen()) {\n <div class=\"app-switcher-dropdown\">\n <div class=\"app-switcher-header\">\n <div>\n <h3>Excellore Apps</h3>\n <p>Switch between available workspaces</p>\n </div>\n </div>\n \n @if (applications().length === 0) {\n <div class=\"app-switcher-empty\">\n No other apps available\n </div>\n } @else {\n <div class=\"app-switcher-list\">\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\"\n >\n <div class=\"app-switcher-app-mark\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"app-switcher-app-content\">\n <div class=\"app-switcher-app-name\" [title]=\"app.name\">{{ app.name }}</div>\n <div class=\"app-switcher-app-url\">{{ app.url }}</div>\n </div>\n <span class=\"app-switcher-open-pill\">\n Open\n </span>\n </a>\n } @else {\n <div class=\"app-switcher-item unavailable\">\n <div class=\"app-switcher-app-mark unavailable\">\n {{ app.initials || getInitials(app.name) }}\n </div>\n <div class=\"app-switcher-app-content\">\n <div class=\"app-switcher-app-name\">{{ app.name }}</div>\n <div class=\"app-switcher-app-url\">{{ app.url }}</div>\n </div>\n <span class=\"app-switcher-soon-pill\">\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-wrapper{position:relative;display:inline-block;text-align:left}.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-trigger-icon{width:1.125rem;height:1.125rem}.app-switcher-dropdown{position:fixed;z-index:50;top:3.75rem;right:.5rem;left:.5rem;max-height:calc(100dvh - 4.5rem);overflow:hidden;border:1px solid var(--border);border-radius:.875rem;background:var(--background);padding:.625rem;box-shadow:0 12px 30px #0f172a24;animation:switcherEnter .15s cubic-bezier(.16,1,.3,1)}.app-switcher-header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);margin-bottom:.375rem;padding:0 .25rem .625rem}.app-switcher-header h3{margin:0;color:var(--secondary);font-size:.65rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.app-switcher-header p{margin:.125rem 0 0;color:var(--secondary);font-size:.72rem;line-height:1.35}.app-switcher-empty{padding:1.5rem .5rem;color:var(--secondary);font-size:.8rem;text-align:center}.app-switcher-list{max-height:calc(100dvh - 10rem);overflow-y:auto;padding:.25rem 0}.app-switcher-item{display:flex;align-items:center;gap:.75rem;border:1px solid transparent;border-radius:.75rem;padding:.65rem;text-decoration:none;transition:background-color .15s ease,border-color .15s ease}.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-link:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.app-switcher-item.unavailable{opacity:.55;-webkit-user-select:none;user-select:none}.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-content{min-width:0;flex:1 1 auto}.app-switcher-app-name{overflow:hidden;color:var(--foreground);font-size:.875rem;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.app-switcher-app-url{overflow:hidden;margin-top:.125rem;color:var(--secondary);font-size:.72rem;text-overflow:ellipsis;white-space:nowrap}.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{flex-shrink:0;border-radius:999px;color:var(--primary);background:color-mix(in srgb,var(--primary) 9%,transparent);padding:.15rem .5rem;font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;opacity:0;transition:opacity .15s ease}.app-switcher-link:hover .app-switcher-open-pill{opacity:1}.app-switcher-soon-pill{flex-shrink:0;border:1px solid var(--border);border-radius:999px;color:var(--secondary);padding:.15rem .5rem;font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.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)}}@media(min-width:640px){.app-switcher-dropdown{position:absolute;top:calc(100% + .5rem);right:0;left:auto;width:22.5rem;max-width:calc(100vw - 1rem);max-height:calc(100dvh - 5rem);transform-origin:top right}.app-switcher-list{max-height:26.25rem}}\n"] }]
|
|
157
|
+
args: [{ selector: 'lib-app-switcher', standalone: true, imports: [CommonModule, LucideGrid], template: "<div class=\"app-switcher-wrapper\">\r\n <button\r\n type=\"button\"\r\n class=\"app-switcher-trigger\"\r\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=\"app-switcher-trigger-icon\"></svg>\r\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"app-switcher-dropdown\">\r\n <div class=\"app-switcher-header\">\r\n <div>\r\n <h3>Excellore Apps</h3>\r\n <p>Switch between available workspaces</p>\r\n </div>\r\n </div>\r\n \r\n @if (applications().length === 0) {\r\n <div class=\"app-switcher-empty\">\r\n No other apps available\r\n </div>\r\n } @else {\r\n <div class=\"app-switcher-list\">\r\n @for (app of applications(); track app.id) {\r\n @if (app.available !== false) {\r\n <a\r\n [href]=\"app.url\"\r\n (click)=\"onSelect(app)\"\r\n class=\"app-switcher-item app-switcher-link\"\r\n >\r\n <div class=\"app-switcher-app-mark\">\r\n {{ app.initials || getInitials(app.name) }}\r\n </div>\r\n <div class=\"app-switcher-app-content\">\r\n <div class=\"app-switcher-app-name\" [title]=\"app.name\">{{ app.name }}</div>\r\n <div class=\"app-switcher-app-url\">{{ app.url }}</div>\r\n </div>\r\n <span class=\"app-switcher-open-pill\">\r\n Open\r\n </span>\r\n </a>\r\n } @else {\r\n <div class=\"app-switcher-item unavailable\">\r\n <div class=\"app-switcher-app-mark unavailable\">\r\n {{ app.initials || getInitials(app.name) }}\r\n </div>\r\n <div class=\"app-switcher-app-content\">\r\n <div class=\"app-switcher-app-name\">{{ app.name }}</div>\r\n <div class=\"app-switcher-app-url\">{{ app.url }}</div>\r\n </div>\r\n <span class=\"app-switcher-soon-pill\">\r\n Soon\r\n </span>\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.app-switcher-wrapper{position:relative;display:inline-block;text-align:left}.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-trigger-icon{width:1.125rem;height:1.125rem}.app-switcher-dropdown{position:fixed;z-index:50;top:3.75rem;right:.5rem;left:.5rem;max-height:calc(100dvh - 4.5rem);overflow:hidden;border:1px solid var(--border);border-radius:.875rem;background:var(--background);padding:.625rem;box-shadow:0 12px 30px #0f172a24;animation:switcherEnter .15s cubic-bezier(.16,1,.3,1)}.app-switcher-header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);margin-bottom:.375rem;padding:0 .25rem .625rem}.app-switcher-header h3{margin:0;color:var(--secondary);font-size:.65rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.app-switcher-header p{margin:.125rem 0 0;color:var(--secondary);font-size:.72rem;line-height:1.35}.app-switcher-empty{padding:1.5rem .5rem;color:var(--secondary);font-size:.8rem;text-align:center}.app-switcher-list{max-height:calc(100dvh - 10rem);overflow-y:auto;padding:.25rem 0}.app-switcher-item{display:flex;align-items:center;gap:.75rem;border:1px solid transparent;border-radius:.75rem;padding:.65rem;text-decoration:none;transition:background-color .15s ease,border-color .15s ease}.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-link:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.app-switcher-item.unavailable{opacity:.55;-webkit-user-select:none;user-select:none}.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-content{min-width:0;flex:1 1 auto}.app-switcher-app-name{overflow:hidden;color:var(--foreground);font-size:.875rem;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.app-switcher-app-url{overflow:hidden;margin-top:.125rem;color:var(--secondary);font-size:.72rem;text-overflow:ellipsis;white-space:nowrap}.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{flex-shrink:0;border-radius:999px;color:var(--primary);background:color-mix(in srgb,var(--primary) 9%,transparent);padding:.15rem .5rem;font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;opacity:0;transition:opacity .15s ease}.app-switcher-link:hover .app-switcher-open-pill{opacity:1}.app-switcher-soon-pill{flex-shrink:0;border:1px solid var(--border);border-radius:999px;color:var(--secondary);padding:.15rem .5rem;font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.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)}}@media(min-width:640px){.app-switcher-dropdown{position:absolute;top:calc(100% + .5rem);right:0;left:auto;width:22.5rem;max-width:calc(100vw - 1rem);max-height:calc(100dvh - 5rem);transform-origin:top right}.app-switcher-list{max-height:26.25rem}}\n"] }]
|
|
158
158
|
}], propDecorators: { applications: [{ type: i0.Input, args: [{ isSignal: true, alias: "applications", required: false }] }], applicationSelected: [{ type: i0.Output, args: ["applicationSelected"] }], onOutsideClick: [{
|
|
159
159
|
type: HostListener,
|
|
160
160
|
args: ['document:click', ['$event']]
|
|
161
161
|
}] } });
|
|
162
162
|
|
|
163
|
-
class
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
class SidebarMenuComponent {
|
|
164
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
165
|
+
errorMessage = input(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
166
|
+
defaultIcon = input('bi-dot', ...(ngDevMode ? [{ debugName: "defaultIcon" }] : /* istanbul ignore next */ []));
|
|
167
|
+
menuItemSelected = output();
|
|
168
|
+
expandedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedIds" }] : /* istanbul ignore next */ []));
|
|
169
|
+
sortedItems = computed(() => this.sortItems(this.items()), ...(ngDevMode ? [{ debugName: "sortedItems" }] : /* istanbul ignore next */ []));
|
|
170
|
+
toggle(item) {
|
|
171
|
+
if (item.disabled) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.expandedIds.update((ids) => {
|
|
175
|
+
const next = new Set(ids);
|
|
176
|
+
if (next.has(item.id)) {
|
|
177
|
+
next.delete(item.id);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
next.add(item.id);
|
|
181
|
+
}
|
|
182
|
+
return next;
|
|
183
|
+
});
|
|
170
184
|
}
|
|
171
|
-
|
|
172
|
-
this.
|
|
185
|
+
isExpanded(item) {
|
|
186
|
+
return this.expandedIds().has(item.id);
|
|
173
187
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this.closeSwitcher();
|
|
188
|
+
hasChildren(item) {
|
|
189
|
+
return !!item.children?.length;
|
|
177
190
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
hasRoute(item) {
|
|
192
|
+
return !!item.route;
|
|
193
|
+
}
|
|
194
|
+
hasHref(item) {
|
|
195
|
+
return !!item.href;
|
|
196
|
+
}
|
|
197
|
+
iconFor(item) {
|
|
198
|
+
return item.icon || this.defaultIcon();
|
|
199
|
+
}
|
|
200
|
+
onSelect(item) {
|
|
201
|
+
if (!item.disabled) {
|
|
202
|
+
this.menuItemSelected.emit(item);
|
|
182
203
|
}
|
|
183
204
|
}
|
|
184
|
-
|
|
185
|
-
|
|
205
|
+
sortedChildren(item) {
|
|
206
|
+
return this.sortItems(item.children ?? []);
|
|
207
|
+
}
|
|
208
|
+
sortItems(items) {
|
|
209
|
+
return [...items]
|
|
210
|
+
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0) || a.label.localeCompare(b.label))
|
|
211
|
+
.map((item) => ({
|
|
212
|
+
...item,
|
|
213
|
+
children: item.children ? this.sortItems(item.children) : undefined,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: SidebarMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
217
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: SidebarMenuComponent, isStandalone: true, selector: "lib-sidebar-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, defaultIcon: { classPropertyName: "defaultIcon", publicName: "defaultIcon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuItemSelected: "menuItemSelected" }, ngImport: i0, template: "<nav class=\"sidebar-menu\" aria-label=\"Sidebar menu\">\n @if (errorMessage()) {\n <div class=\"sidebar-menu__error\" role=\"alert\">\n {{ errorMessage() }}\n </div>\r\n }\r\n\r\n <ng-template #menuList let-menuItems let-level=\"level\">\r\n @for (item of menuItems; track item.id) {\r\n @if (hasChildren(item)) {\r\n <div class=\"sidebar-menu__group\">\r\n <button\r\n type=\"button\"\r\n class=\"sidebar-menu__item sidebar-menu__button\"\r\n [class.disabled]=\"item.disabled\"\r\n [class.expanded]=\"isExpanded(item)\"\r\n [style.padding-left.rem]=\"0.75 + level * 0.75\"\r\n [attr.aria-expanded]=\"isExpanded(item)\"\r\n (click)=\"toggle(item)\"\r\n >\r\n <span class=\"sidebar-menu__item-main\">\r\n <i class=\"bi sidebar-menu__icon\" [ngClass]=\"iconFor(item)\" aria-hidden=\"true\"></i>\r\n <span class=\"sidebar-menu__label\">{{ item.label }}</span>\r\n </span>\r\n <svg class=\"sidebar-menu__chevron\" [class.expanded]=\"isExpanded(item)\" viewBox=\"0 0 20 20\" aria-hidden=\"true\">\r\n <path d=\"M6 8l4 4 4-4\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.8\" />\r\n </svg>\r\n </button>\r\n\r\n @if (isExpanded(item)) {\r\n <div class=\"sidebar-menu__children\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"menuList\"\r\n [ngTemplateOutletContext]=\"{ $implicit: sortedChildren(item), level: level + 1 }\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @else if (hasRoute(item)) {\r\n <a\r\n [routerLink]=\"item.route\"\r\n routerLinkActive=\"active\"\r\n [routerLinkActiveOptions]=\"{ exact: item.exact ?? true }\"\r\n class=\"sidebar-menu__item\"\r\n [class.disabled]=\"item.disabled\"\r\n [style.padding-left.rem]=\"0.75 + level * 0.75\"\r\n (click)=\"onSelect(item)\"\r\n >\r\n <i class=\"bi sidebar-menu__icon\" [ngClass]=\"iconFor(item)\" aria-hidden=\"true\"></i>\r\n <span class=\"sidebar-menu__label\">{{ item.label }}</span>\r\n </a>\r\n } @else if (hasHref(item)) {\r\n <a\r\n [href]=\"item.href\"\r\n [target]=\"item.target || '_self'\"\r\n class=\"sidebar-menu__item\"\r\n [class.disabled]=\"item.disabled\"\r\n [style.padding-left.rem]=\"0.75 + level * 0.75\"\r\n (click)=\"onSelect(item)\"\r\n >\r\n <i class=\"bi sidebar-menu__icon\" [ngClass]=\"iconFor(item)\" aria-hidden=\"true\"></i>\r\n <span class=\"sidebar-menu__label\">{{ item.label }}</span>\r\n </a>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"sidebar-menu__item sidebar-menu__button\"\r\n [class.disabled]=\"item.disabled\"\r\n [style.padding-left.rem]=\"0.75 + level * 0.75\"\r\n (click)=\"onSelect(item)\"\r\n >\r\n <span class=\"sidebar-menu__item-main\">\r\n <i class=\"bi sidebar-menu__icon\" [ngClass]=\"iconFor(item)\" aria-hidden=\"true\"></i>\r\n <span class=\"sidebar-menu__label\">{{ item.label }}</span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </ng-template>\r\n\r\n <ng-container [ngTemplateOutlet]=\"menuList\" [ngTemplateOutletContext]=\"{ $implicit: sortedItems(), level: 0 }\" />\r\n</nav>\r\n", styles: [":host{display:block;height:100%;min-width:0;min-height:0}.sidebar-menu{display:flex;flex-direction:column;gap:.25rem;height:100%;min-height:0;overflow-x:hidden;overflow-y:auto;padding:.5rem}.sidebar-menu__item{display:flex;min-height:2.5rem;width:100%;align-items:center;gap:.625rem;border:0;border-radius:.375rem;background:transparent;color:var(--bs-body-color, var(--foreground, #111827));cursor:pointer;font:inherit;padding:.625rem .75rem;text-align:left;text-decoration:none;transition:background-color .15s ease,color .15s ease}.sidebar-menu__item:hover,.sidebar-menu__item.expanded{background:var(--bs-secondary-bg, color-mix(in srgb, var(--border, #e5e7eb) 65%, transparent));color:var(--bs-body-color, var(--foreground, #111827))}.sidebar-menu__item.active{background:var(--accent-light, color-mix(in srgb, var(--accent-primary, #0067b8) 12%, transparent));color:var(--accent-primary, #0067b8)}.sidebar-menu__button{justify-content:space-between}.sidebar-menu__item-main{display:inline-flex;min-width:0;align-items:center;gap:.625rem}.sidebar-menu__icon{width:1.1rem;min-width:1.1rem;font-size:1.1rem;line-height:1;text-align:center}.sidebar-menu__label{min-width:0;overflow:hidden;font-size:.875rem;font-weight:500;text-overflow:ellipsis;white-space:nowrap}.sidebar-menu__chevron{height:1rem;width:1rem;color:var(--text-secondary, var(--secondary, #6b7280));transition:transform .2s ease}.sidebar-menu__chevron.expanded{transform:rotate(180deg)}.sidebar-menu__children{display:grid;gap:.25rem;margin-top:.25rem}.sidebar-menu__error{border-radius:.375rem;color:var(--danger, #dc2626);font-size:.875rem;padding:.5rem .75rem}.sidebar-menu__item.disabled{cursor:not-allowed;opacity:.45;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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"] }] });
|
|
186
218
|
}
|
|
187
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type:
|
|
219
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: SidebarMenuComponent, decorators: [{
|
|
188
220
|
type: Component,
|
|
189
|
-
args: [{ selector: 'lib-
|
|
190
|
-
}], propDecorators: {
|
|
191
|
-
type: HostListener,
|
|
192
|
-
args: ['document:click', ['$event']]
|
|
193
|
-
}] } });
|
|
221
|
+
args: [{ selector: 'lib-sidebar-menu', standalone: true, imports: [NgClass, NgTemplateOutlet, RouterLink, RouterLinkActive], template: "<nav class=\"sidebar-menu\" aria-label=\"Sidebar menu\">\n @if (errorMessage()) {\n <div class=\"sidebar-menu__error\" role=\"alert\">\n {{ errorMessage() }}\n </div>\r\n }\r\n\r\n <ng-template #menuList let-menuItems let-level=\"level\">\r\n @for (item of menuItems; track item.id) {\r\n @if (hasChildren(item)) {\r\n <div class=\"sidebar-menu__group\">\r\n <button\r\n type=\"button\"\r\n class=\"sidebar-menu__item sidebar-menu__button\"\r\n [class.disabled]=\"item.disabled\"\r\n [class.expanded]=\"isExpanded(item)\"\r\n [style.padding-left.rem]=\"0.75 + level * 0.75\"\r\n [attr.aria-expanded]=\"isExpanded(item)\"\r\n (click)=\"toggle(item)\"\r\n >\r\n <span class=\"sidebar-menu__item-main\">\r\n <i class=\"bi sidebar-menu__icon\" [ngClass]=\"iconFor(item)\" aria-hidden=\"true\"></i>\r\n <span class=\"sidebar-menu__label\">{{ item.label }}</span>\r\n </span>\r\n <svg class=\"sidebar-menu__chevron\" [class.expanded]=\"isExpanded(item)\" viewBox=\"0 0 20 20\" aria-hidden=\"true\">\r\n <path d=\"M6 8l4 4 4-4\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.8\" />\r\n </svg>\r\n </button>\r\n\r\n @if (isExpanded(item)) {\r\n <div class=\"sidebar-menu__children\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"menuList\"\r\n [ngTemplateOutletContext]=\"{ $implicit: sortedChildren(item), level: level + 1 }\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @else if (hasRoute(item)) {\r\n <a\r\n [routerLink]=\"item.route\"\r\n routerLinkActive=\"active\"\r\n [routerLinkActiveOptions]=\"{ exact: item.exact ?? true }\"\r\n class=\"sidebar-menu__item\"\r\n [class.disabled]=\"item.disabled\"\r\n [style.padding-left.rem]=\"0.75 + level * 0.75\"\r\n (click)=\"onSelect(item)\"\r\n >\r\n <i class=\"bi sidebar-menu__icon\" [ngClass]=\"iconFor(item)\" aria-hidden=\"true\"></i>\r\n <span class=\"sidebar-menu__label\">{{ item.label }}</span>\r\n </a>\r\n } @else if (hasHref(item)) {\r\n <a\r\n [href]=\"item.href\"\r\n [target]=\"item.target || '_self'\"\r\n class=\"sidebar-menu__item\"\r\n [class.disabled]=\"item.disabled\"\r\n [style.padding-left.rem]=\"0.75 + level * 0.75\"\r\n (click)=\"onSelect(item)\"\r\n >\r\n <i class=\"bi sidebar-menu__icon\" [ngClass]=\"iconFor(item)\" aria-hidden=\"true\"></i>\r\n <span class=\"sidebar-menu__label\">{{ item.label }}</span>\r\n </a>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"sidebar-menu__item sidebar-menu__button\"\r\n [class.disabled]=\"item.disabled\"\r\n [style.padding-left.rem]=\"0.75 + level * 0.75\"\r\n (click)=\"onSelect(item)\"\r\n >\r\n <span class=\"sidebar-menu__item-main\">\r\n <i class=\"bi sidebar-menu__icon\" [ngClass]=\"iconFor(item)\" aria-hidden=\"true\"></i>\r\n <span class=\"sidebar-menu__label\">{{ item.label }}</span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </ng-template>\r\n\r\n <ng-container [ngTemplateOutlet]=\"menuList\" [ngTemplateOutletContext]=\"{ $implicit: sortedItems(), level: 0 }\" />\r\n</nav>\r\n", styles: [":host{display:block;height:100%;min-width:0;min-height:0}.sidebar-menu{display:flex;flex-direction:column;gap:.25rem;height:100%;min-height:0;overflow-x:hidden;overflow-y:auto;padding:.5rem}.sidebar-menu__item{display:flex;min-height:2.5rem;width:100%;align-items:center;gap:.625rem;border:0;border-radius:.375rem;background:transparent;color:var(--bs-body-color, var(--foreground, #111827));cursor:pointer;font:inherit;padding:.625rem .75rem;text-align:left;text-decoration:none;transition:background-color .15s ease,color .15s ease}.sidebar-menu__item:hover,.sidebar-menu__item.expanded{background:var(--bs-secondary-bg, color-mix(in srgb, var(--border, #e5e7eb) 65%, transparent));color:var(--bs-body-color, var(--foreground, #111827))}.sidebar-menu__item.active{background:var(--accent-light, color-mix(in srgb, var(--accent-primary, #0067b8) 12%, transparent));color:var(--accent-primary, #0067b8)}.sidebar-menu__button{justify-content:space-between}.sidebar-menu__item-main{display:inline-flex;min-width:0;align-items:center;gap:.625rem}.sidebar-menu__icon{width:1.1rem;min-width:1.1rem;font-size:1.1rem;line-height:1;text-align:center}.sidebar-menu__label{min-width:0;overflow:hidden;font-size:.875rem;font-weight:500;text-overflow:ellipsis;white-space:nowrap}.sidebar-menu__chevron{height:1rem;width:1rem;color:var(--text-secondary, var(--secondary, #6b7280));transition:transform .2s ease}.sidebar-menu__chevron.expanded{transform:rotate(180deg)}.sidebar-menu__children{display:grid;gap:.25rem;margin-top:.25rem}.sidebar-menu__error{border-radius:.375rem;color:var(--danger, #dc2626);font-size:.875rem;padding:.5rem .75rem}.sidebar-menu__item.disabled{cursor:not-allowed;opacity:.45;pointer-events:none}\n"] }]
|
|
222
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], defaultIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultIcon", required: false }] }], menuItemSelected: [{ type: i0.Output, args: ["menuItemSelected"] }] } });
|
|
194
223
|
|
|
195
224
|
class NotificationPanelComponent {
|
|
196
225
|
notifications = input([], ...(ngDevMode ? [{ debugName: "notifications" }] : /* istanbul ignore next */ []));
|
|
@@ -226,11 +255,11 @@ class NotificationPanelComponent {
|
|
|
226
255
|
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
227
256
|
}
|
|
228
257
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NotificationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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]" }] });
|
|
258
|
+
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>\r\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>\r\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>\r\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]" }] });
|
|
230
259
|
}
|
|
231
260
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NotificationPanelComponent, decorators: [{
|
|
232
261
|
type: Component,
|
|
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"] }]
|
|
262
|
+
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>\r\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>\r\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>\r\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"] }]
|
|
234
263
|
}], 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"] }] } });
|
|
235
264
|
|
|
236
265
|
class NotificationBellComponent {
|
|
@@ -263,11 +292,11 @@ class NotificationBellComponent {
|
|
|
263
292
|
}
|
|
264
293
|
}
|
|
265
294
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NotificationBellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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]" }] });
|
|
295
|
+
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\r\n type=\"button\"\r\n class=\"notification-trigger\"\r\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>\r\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\">\r\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]" }] });
|
|
267
296
|
}
|
|
268
297
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: NotificationBellComponent, decorators: [{
|
|
269
298
|
type: Component,
|
|
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"] }]
|
|
299
|
+
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\r\n type=\"button\"\r\n class=\"notification-trigger\"\r\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>\r\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\">\r\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"] }]
|
|
271
300
|
}], 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: [{
|
|
272
301
|
type: HostListener,
|
|
273
302
|
args: ['document:click', ['$event']]
|
|
@@ -295,11 +324,11 @@ class UserAvatarComponent {
|
|
|
295
324
|
this.imageError = true;
|
|
296
325
|
}
|
|
297
326
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: UserAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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\">\n @if (user()?.avatar && !imageError) {\n <img [src]=\"user()?.avatar\" [alt]=\"user()?.name || 'User avatar'\" class=\"avatar-img\" (error)=\"onImageError()\" />\n } @else {\n <span class=\"avatar-initials\">{{ initials() }}</span>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.avatar-container{display:grid;width:100%;height:100%;place-items:center;overflow:hidden;border:1px solid var(--border);border-radius:999px;background:linear-gradient(135deg,var(--primary, #0067b8) 0%,var(--secondary, #005a9e) 100%);color:var(--background, #ffffff);box-shadow:0 1px 3px #0000000d;-webkit-user-select:none;user-select:none;transition:box-shadow .2s ease,transform .2s ease}.avatar-img{display:block;width:100%;height:100%;object-fit:cover}:host:hover .avatar-container{transform:scale(1.02);box-shadow:0 4px 6px -1px #00000014,0 2px 4px -1px #0000000a}.avatar-initials{font-size:.75rem;font-weight:750;line-height:1;text-transform:uppercase;text-shadow:0 1px 1px rgba(0,0,0,.15)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
327
|
+
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\">\r\n @if (user()?.avatar && !imageError) {\r\n <img [src]=\"user()?.avatar\" [alt]=\"user()?.name || 'User avatar'\" class=\"avatar-img\" (error)=\"onImageError()\" />\r\n } @else {\r\n <span class=\"avatar-initials\">{{ initials() }}</span>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%}.avatar-container{display:grid;width:100%;height:100%;place-items:center;overflow:hidden;border:1px solid var(--border);border-radius:999px;background:linear-gradient(135deg,var(--primary, #0067b8) 0%,var(--secondary, #005a9e) 100%);color:var(--background, #ffffff);box-shadow:0 1px 3px #0000000d;-webkit-user-select:none;user-select:none;transition:box-shadow .2s ease,transform .2s ease}.avatar-img{display:block;width:100%;height:100%;object-fit:cover}:host:hover .avatar-container{transform:scale(1.02);box-shadow:0 4px 6px -1px #00000014,0 2px 4px -1px #0000000a}.avatar-initials{font-size:.75rem;font-weight:750;line-height:1;text-transform:uppercase;text-shadow:0 1px 1px rgba(0,0,0,.15)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
299
328
|
}
|
|
300
329
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: UserAvatarComponent, decorators: [{
|
|
301
330
|
type: Component,
|
|
302
|
-
args: [{ selector: 'lib-user-avatar', standalone: true, imports: [CommonModule], template: "<div class=\"avatar-container\">\n @if (user()?.avatar && !imageError) {\n <img [src]=\"user()?.avatar\" [alt]=\"user()?.name || 'User avatar'\" class=\"avatar-img\" (error)=\"onImageError()\" />\n } @else {\n <span class=\"avatar-initials\">{{ initials() }}</span>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.avatar-container{display:grid;width:100%;height:100%;place-items:center;overflow:hidden;border:1px solid var(--border);border-radius:999px;background:linear-gradient(135deg,var(--primary, #0067b8) 0%,var(--secondary, #005a9e) 100%);color:var(--background, #ffffff);box-shadow:0 1px 3px #0000000d;-webkit-user-select:none;user-select:none;transition:box-shadow .2s ease,transform .2s ease}.avatar-img{display:block;width:100%;height:100%;object-fit:cover}:host:hover .avatar-container{transform:scale(1.02);box-shadow:0 4px 6px -1px #00000014,0 2px 4px -1px #0000000a}.avatar-initials{font-size:.75rem;font-weight:750;line-height:1;text-transform:uppercase;text-shadow:0 1px 1px rgba(0,0,0,.15)}\n"] }]
|
|
331
|
+
args: [{ selector: 'lib-user-avatar', standalone: true, imports: [CommonModule], template: "<div class=\"avatar-container\">\r\n @if (user()?.avatar && !imageError) {\r\n <img [src]=\"user()?.avatar\" [alt]=\"user()?.name || 'User avatar'\" class=\"avatar-img\" (error)=\"onImageError()\" />\r\n } @else {\r\n <span class=\"avatar-initials\">{{ initials() }}</span>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%}.avatar-container{display:grid;width:100%;height:100%;place-items:center;overflow:hidden;border:1px solid var(--border);border-radius:999px;background:linear-gradient(135deg,var(--primary, #0067b8) 0%,var(--secondary, #005a9e) 100%);color:var(--background, #ffffff);box-shadow:0 1px 3px #0000000d;-webkit-user-select:none;user-select:none;transition:box-shadow .2s ease,transform .2s ease}.avatar-img{display:block;width:100%;height:100%;object-fit:cover}:host:hover .avatar-container{transform:scale(1.02);box-shadow:0 4px 6px -1px #00000014,0 2px 4px -1px #0000000a}.avatar-initials{font-size:.75rem;font-weight:750;line-height:1;text-transform:uppercase;text-shadow:0 1px 1px rgba(0,0,0,.15)}\n"] }]
|
|
303
332
|
}], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }] } });
|
|
304
333
|
|
|
305
334
|
class UserMenuComponent {
|
|
@@ -324,11 +353,11 @@ class UserMenuComponent {
|
|
|
324
353
|
}
|
|
325
354
|
}
|
|
326
355
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: UserMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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\">\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=\"user-menu-avatar\">\n <lib-user-avatar [user]=\"user()\" />\n </div>\n <svg lucideChevronDown class=\"user-menu-chevron\"\n [class.rotate-180]=\"isOpen()\"></svg>\n </button>\n\n @if (isOpen()) {\n <div class=\"user-dropdown-panel\">\n @if (user(); as u) {\n <div class=\"user-dropdown-profile\">\n <p class=\"user-dropdown-name\" [title]=\"u.name\">{{ u.name }}</p>\n <p class=\"user-dropdown-email\" [title]=\"u.email\">{{ u.email }}</p>\n </div>\n }\n\n <div class=\"user-menu-list\">\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('profile')\">\n <svg lucideUser class=\"user-menu-icon\"></svg>\n <span>My Profile</span>\n </button>\n\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('settings')\">\n <svg lucideSettings class=\"user-menu-icon\"></svg>\n <span>Settings</span>\n </button>\n\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('billing')\">\n <svg lucideCreditCard class=\"user-menu-icon\"></svg>\n <span>Billing</span>\n </button>\n </div>\n\n <div class=\"user-menu-danger-zone\">\n <button type=\"button\" class=\"user-menu-item danger\" (click)=\"onAction('logout')\">\n <svg lucideLogOut class=\"user-menu-icon\"></svg>\n <span>Sign Out</span>\n </button>\n </div>\r\n </div>\r\n }\r\n</div>\n", styles: [":host{display:inline-block}.user-menu-wrapper{position:relative;display:inline-block;text-align:left}.user-menu-trigger{display:flex;align-items:center;justify-content:center;gap:.35rem;min-height:2.25rem;border:1px solid var(--border);border-radius:999px;color:var(--foreground);background:var(--background);padding:.2rem .5rem .2rem .2rem;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.user-menu-avatar{display:block;width:1.65rem;height:1.65rem;flex:0 0 1.65rem;overflow:hidden;border-radius:999px}.user-menu-chevron{width:.875rem;height:.875rem;color:var(--secondary);transition:transform .2s ease}.user-menu-chevron.rotate-180{transform:rotate(180deg)}.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{position:fixed;z-index:50;top:3.75rem;right:.5rem;left:.5rem;overflow:hidden;border:1px solid var(--border);border-radius:.875rem;background:var(--background);padding:.375rem;box-shadow:0 12px 30px #0f172a24;animation:dropdownEnter .15s cubic-bezier(.16,1,.3,1)}.user-dropdown-profile{border-bottom:1px solid var(--border);margin-bottom:.25rem;padding:.65rem .75rem}.user-dropdown-name,.user-dropdown-email{overflow:hidden;margin:0;text-overflow:ellipsis;white-space:nowrap}.user-dropdown-name{color:var(--foreground);font-size:.875rem;font-weight:750}.user-dropdown-email{margin-top:.125rem;color:var(--secondary);font-size:.78rem}.user-menu-list{display:grid;gap:.125rem;padding:.125rem 0}.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-icon{width:1rem;height:1rem;flex-shrink:0;color:var(--secondary)}.user-menu-item:hover{background:color-mix(in srgb,var(--border) 32%,transparent)}.user-menu-item.danger,.user-menu-item.danger .user-menu-icon{color:var(--danger, #ef4444)}.user-menu-item.danger:hover{background:color-mix(in srgb,var(--danger, #ef4444) 10%,transparent)}.user-menu-danger-zone{border-top:1px solid var(--border);margin-top:.25rem;padding-top:.25rem}@keyframes dropdownEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}@media(min-width:640px){.user-dropdown-panel{position:absolute;top:calc(100% + .5rem);right:0;left:auto;width:14rem;max-width:calc(100vw - 1rem);transform-origin:top right}}\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]" }] });
|
|
356
|
+
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\">\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=\"user-menu-avatar\">\r\n <lib-user-avatar [user]=\"user()\" />\r\n </div>\r\n <svg lucideChevronDown class=\"user-menu-chevron\"\r\n [class.rotate-180]=\"isOpen()\"></svg>\r\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"user-dropdown-panel\">\r\n @if (user(); as u) {\r\n <div class=\"user-dropdown-profile\">\r\n <p class=\"user-dropdown-name\" [title]=\"u.name\">{{ u.name }}</p>\r\n <p class=\"user-dropdown-email\" [title]=\"u.email\">{{ u.email }}</p>\r\n </div>\r\n }\r\n\r\n <div class=\"user-menu-list\">\r\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('profile')\">\r\n <svg lucideUser class=\"user-menu-icon\"></svg>\r\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=\"user-menu-icon\"></svg>\r\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=\"user-menu-icon\"></svg>\r\n <span>Billing</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"user-menu-danger-zone\">\r\n <button type=\"button\" class=\"user-menu-item danger\" (click)=\"onAction('logout')\">\r\n <svg lucideLogOut class=\"user-menu-icon\"></svg>\r\n <span>Sign Out</span>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.user-menu-wrapper{position:relative;display:inline-block;text-align:left}.user-menu-trigger{display:flex;align-items:center;justify-content:center;gap:.35rem;min-height:2.25rem;border:1px solid var(--border);border-radius:999px;color:var(--foreground);background:var(--background);padding:.2rem .5rem .2rem .2rem;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.user-menu-avatar{display:block;width:1.65rem;height:1.65rem;flex:0 0 1.65rem;overflow:hidden;border-radius:999px}.user-menu-chevron{width:.875rem;height:.875rem;color:var(--secondary);transition:transform .2s ease}.user-menu-chevron.rotate-180{transform:rotate(180deg)}.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{position:fixed;z-index:50;top:3.75rem;right:.5rem;left:.5rem;overflow:hidden;border:1px solid var(--border);border-radius:.875rem;background:var(--background);padding:.375rem;box-shadow:0 12px 30px #0f172a24;animation:dropdownEnter .15s cubic-bezier(.16,1,.3,1)}.user-dropdown-profile{border-bottom:1px solid var(--border);margin-bottom:.25rem;padding:.65rem .75rem}.user-dropdown-name,.user-dropdown-email{overflow:hidden;margin:0;text-overflow:ellipsis;white-space:nowrap}.user-dropdown-name{color:var(--foreground);font-size:.875rem;font-weight:750}.user-dropdown-email{margin-top:.125rem;color:var(--secondary);font-size:.78rem}.user-menu-list{display:grid;gap:.125rem;padding:.125rem 0}.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-icon{width:1rem;height:1rem;flex-shrink:0;color:var(--secondary)}.user-menu-item:hover{background:color-mix(in srgb,var(--border) 32%,transparent)}.user-menu-item.danger,.user-menu-item.danger .user-menu-icon{color:var(--danger, #ef4444)}.user-menu-item.danger:hover{background:color-mix(in srgb,var(--danger, #ef4444) 10%,transparent)}.user-menu-danger-zone{border-top:1px solid var(--border);margin-top:.25rem;padding-top:.25rem}@keyframes dropdownEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}@media(min-width:640px){.user-dropdown-panel{position:absolute;top:calc(100% + .5rem);right:0;left:auto;width:14rem;max-width:calc(100vw - 1rem);transform-origin:top right}}\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]" }] });
|
|
328
357
|
}
|
|
329
358
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: UserMenuComponent, decorators: [{
|
|
330
359
|
type: Component,
|
|
331
|
-
args: [{ selector: 'lib-user-menu', standalone: true, imports: [CommonModule, UserAvatarComponent, LucideChevronDown, LucideCreditCard, LucideLogOut, LucideSettings, LucideUser], template: "<div class=\"user-menu-wrapper\">\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=\"user-menu-avatar\">\n <lib-user-avatar [user]=\"user()\" />\n </div>\n <svg lucideChevronDown class=\"user-menu-chevron\"\n [class.rotate-180]=\"isOpen()\"></svg>\n </button>\n\n @if (isOpen()) {\n <div class=\"user-dropdown-panel\">\n @if (user(); as u) {\n <div class=\"user-dropdown-profile\">\n <p class=\"user-dropdown-name\" [title]=\"u.name\">{{ u.name }}</p>\n <p class=\"user-dropdown-email\" [title]=\"u.email\">{{ u.email }}</p>\n </div>\n }\n\n <div class=\"user-menu-list\">\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('profile')\">\n <svg lucideUser class=\"user-menu-icon\"></svg>\n <span>My Profile</span>\n </button>\n\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('settings')\">\n <svg lucideSettings class=\"user-menu-icon\"></svg>\n <span>Settings</span>\n </button>\n\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('billing')\">\n <svg lucideCreditCard class=\"user-menu-icon\"></svg>\n <span>Billing</span>\n </button>\n </div>\n\n <div class=\"user-menu-danger-zone\">\n <button type=\"button\" class=\"user-menu-item danger\" (click)=\"onAction('logout')\">\n <svg lucideLogOut class=\"user-menu-icon\"></svg>\n <span>Sign Out</span>\n </button>\n </div>\r\n </div>\r\n }\r\n</div>\n", styles: [":host{display:inline-block}.user-menu-wrapper{position:relative;display:inline-block;text-align:left}.user-menu-trigger{display:flex;align-items:center;justify-content:center;gap:.35rem;min-height:2.25rem;border:1px solid var(--border);border-radius:999px;color:var(--foreground);background:var(--background);padding:.2rem .5rem .2rem .2rem;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.user-menu-avatar{display:block;width:1.65rem;height:1.65rem;flex:0 0 1.65rem;overflow:hidden;border-radius:999px}.user-menu-chevron{width:.875rem;height:.875rem;color:var(--secondary);transition:transform .2s ease}.user-menu-chevron.rotate-180{transform:rotate(180deg)}.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{position:fixed;z-index:50;top:3.75rem;right:.5rem;left:.5rem;overflow:hidden;border:1px solid var(--border);border-radius:.875rem;background:var(--background);padding:.375rem;box-shadow:0 12px 30px #0f172a24;animation:dropdownEnter .15s cubic-bezier(.16,1,.3,1)}.user-dropdown-profile{border-bottom:1px solid var(--border);margin-bottom:.25rem;padding:.65rem .75rem}.user-dropdown-name,.user-dropdown-email{overflow:hidden;margin:0;text-overflow:ellipsis;white-space:nowrap}.user-dropdown-name{color:var(--foreground);font-size:.875rem;font-weight:750}.user-dropdown-email{margin-top:.125rem;color:var(--secondary);font-size:.78rem}.user-menu-list{display:grid;gap:.125rem;padding:.125rem 0}.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-icon{width:1rem;height:1rem;flex-shrink:0;color:var(--secondary)}.user-menu-item:hover{background:color-mix(in srgb,var(--border) 32%,transparent)}.user-menu-item.danger,.user-menu-item.danger .user-menu-icon{color:var(--danger, #ef4444)}.user-menu-item.danger:hover{background:color-mix(in srgb,var(--danger, #ef4444) 10%,transparent)}.user-menu-danger-zone{border-top:1px solid var(--border);margin-top:.25rem;padding-top:.25rem}@keyframes dropdownEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}@media(min-width:640px){.user-dropdown-panel{position:absolute;top:calc(100% + .5rem);right:0;left:auto;width:14rem;max-width:calc(100vw - 1rem);transform-origin:top right}}\n"] }]
|
|
360
|
+
args: [{ selector: 'lib-user-menu', standalone: true, imports: [CommonModule, UserAvatarComponent, LucideChevronDown, LucideCreditCard, LucideLogOut, LucideSettings, LucideUser], template: "<div class=\"user-menu-wrapper\">\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=\"user-menu-avatar\">\r\n <lib-user-avatar [user]=\"user()\" />\r\n </div>\r\n <svg lucideChevronDown class=\"user-menu-chevron\"\r\n [class.rotate-180]=\"isOpen()\"></svg>\r\n </button>\r\n\r\n @if (isOpen()) {\r\n <div class=\"user-dropdown-panel\">\r\n @if (user(); as u) {\r\n <div class=\"user-dropdown-profile\">\r\n <p class=\"user-dropdown-name\" [title]=\"u.name\">{{ u.name }}</p>\r\n <p class=\"user-dropdown-email\" [title]=\"u.email\">{{ u.email }}</p>\r\n </div>\r\n }\r\n\r\n <div class=\"user-menu-list\">\r\n <button type=\"button\" class=\"user-menu-item\" (click)=\"onAction('profile')\">\r\n <svg lucideUser class=\"user-menu-icon\"></svg>\r\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=\"user-menu-icon\"></svg>\r\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=\"user-menu-icon\"></svg>\r\n <span>Billing</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"user-menu-danger-zone\">\r\n <button type=\"button\" class=\"user-menu-item danger\" (click)=\"onAction('logout')\">\r\n <svg lucideLogOut class=\"user-menu-icon\"></svg>\r\n <span>Sign Out</span>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:inline-block}.user-menu-wrapper{position:relative;display:inline-block;text-align:left}.user-menu-trigger{display:flex;align-items:center;justify-content:center;gap:.35rem;min-height:2.25rem;border:1px solid var(--border);border-radius:999px;color:var(--foreground);background:var(--background);padding:.2rem .5rem .2rem .2rem;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.user-menu-avatar{display:block;width:1.65rem;height:1.65rem;flex:0 0 1.65rem;overflow:hidden;border-radius:999px}.user-menu-chevron{width:.875rem;height:.875rem;color:var(--secondary);transition:transform .2s ease}.user-menu-chevron.rotate-180{transform:rotate(180deg)}.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{position:fixed;z-index:50;top:3.75rem;right:.5rem;left:.5rem;overflow:hidden;border:1px solid var(--border);border-radius:.875rem;background:var(--background);padding:.375rem;box-shadow:0 12px 30px #0f172a24;animation:dropdownEnter .15s cubic-bezier(.16,1,.3,1)}.user-dropdown-profile{border-bottom:1px solid var(--border);margin-bottom:.25rem;padding:.65rem .75rem}.user-dropdown-name,.user-dropdown-email{overflow:hidden;margin:0;text-overflow:ellipsis;white-space:nowrap}.user-dropdown-name{color:var(--foreground);font-size:.875rem;font-weight:750}.user-dropdown-email{margin-top:.125rem;color:var(--secondary);font-size:.78rem}.user-menu-list{display:grid;gap:.125rem;padding:.125rem 0}.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-icon{width:1rem;height:1rem;flex-shrink:0;color:var(--secondary)}.user-menu-item:hover{background:color-mix(in srgb,var(--border) 32%,transparent)}.user-menu-item.danger,.user-menu-item.danger .user-menu-icon{color:var(--danger, #ef4444)}.user-menu-item.danger:hover{background:color-mix(in srgb,var(--danger, #ef4444) 10%,transparent)}.user-menu-danger-zone{border-top:1px solid var(--border);margin-top:.25rem;padding-top:.25rem}@keyframes dropdownEnter{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}@media(min-width:640px){.user-dropdown-panel{position:absolute;top:calc(100% + .5rem);right:0;left:auto;width:14rem;max-width:calc(100vw - 1rem);transform-origin:top right}}\n"] }]
|
|
332
361
|
}], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }], menuAction: [{ type: i0.Output, args: ["menuAction"] }], onOutsideClick: [{
|
|
333
362
|
type: HostListener,
|
|
334
363
|
args: ['document:click', ['$event']]
|
|
@@ -341,39 +370,39 @@ class IconComponent {
|
|
|
341
370
|
color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
342
371
|
strokeWidth = input(undefined, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
|
|
343
372
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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
|
-
}
|
|
373
|
+
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: `
|
|
374
|
+
@switch (name()) {
|
|
375
|
+
@case ('menu') {
|
|
376
|
+
<svg lucideMenu [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
377
|
+
}
|
|
378
|
+
@case ('x') {
|
|
379
|
+
<svg lucideX [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
380
|
+
}
|
|
381
|
+
@case ('sun') {
|
|
382
|
+
<svg lucideSun [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
383
|
+
}
|
|
384
|
+
@case ('moon') {
|
|
385
|
+
<svg lucideMoon [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
386
|
+
}
|
|
387
|
+
@case ('monitor') {
|
|
388
|
+
<svg lucideMonitor [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
389
|
+
}
|
|
390
|
+
@case ('bell') {
|
|
391
|
+
<svg lucideBell [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
392
|
+
}
|
|
393
|
+
@case ('grid') {
|
|
394
|
+
<svg lucideGrid [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
395
|
+
}
|
|
396
|
+
@case ('chevronDown') {
|
|
397
|
+
<svg lucideChevronDown [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
398
|
+
}
|
|
399
|
+
@case ('building2') {
|
|
400
|
+
<svg lucideBuilding2 [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
401
|
+
}
|
|
402
|
+
@default {
|
|
403
|
+
<svg lucideSearch [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
404
|
+
}
|
|
405
|
+
}
|
|
377
406
|
`, 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]" }] });
|
|
378
407
|
}
|
|
379
408
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: IconComponent, decorators: [{
|
|
@@ -390,49 +419,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
390
419
|
LucideSearch,
|
|
391
420
|
LucideSun,
|
|
392
421
|
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
|
-
}
|
|
422
|
+
], template: `
|
|
423
|
+
@switch (name()) {
|
|
424
|
+
@case ('menu') {
|
|
425
|
+
<svg lucideMenu [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
426
|
+
}
|
|
427
|
+
@case ('x') {
|
|
428
|
+
<svg lucideX [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
429
|
+
}
|
|
430
|
+
@case ('sun') {
|
|
431
|
+
<svg lucideSun [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
432
|
+
}
|
|
433
|
+
@case ('moon') {
|
|
434
|
+
<svg lucideMoon [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
435
|
+
}
|
|
436
|
+
@case ('monitor') {
|
|
437
|
+
<svg lucideMonitor [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
438
|
+
}
|
|
439
|
+
@case ('bell') {
|
|
440
|
+
<svg lucideBell [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
441
|
+
}
|
|
442
|
+
@case ('grid') {
|
|
443
|
+
<svg lucideGrid [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
444
|
+
}
|
|
445
|
+
@case ('chevronDown') {
|
|
446
|
+
<svg lucideChevronDown [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
447
|
+
}
|
|
448
|
+
@case ('building2') {
|
|
449
|
+
<svg lucideBuilding2 [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
450
|
+
}
|
|
451
|
+
@default {
|
|
452
|
+
<svg lucideSearch [size]="size()" [color]="color()" [strokeWidth]="strokeWidth()" class="lib-icon"></svg>
|
|
453
|
+
}
|
|
454
|
+
}
|
|
426
455
|
`, styles: [":host{display:inline-flex;align-items:center;justify-content:center;color:inherit}.lib-icon{display:block;color:inherit}\n"] }]
|
|
427
456
|
}], 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 }] }] } });
|
|
428
457
|
|
|
429
458
|
class HeaderComponent {
|
|
430
459
|
router = inject(Router);
|
|
431
460
|
themeService = inject(ThemeService);
|
|
432
|
-
// User
|
|
433
|
-
user = input(
|
|
461
|
+
// User Inputs
|
|
462
|
+
user = input.required(...(ngDevMode ? [{ debugName: "user" }] : /* istanbul ignore next */ []));
|
|
434
463
|
permissions = input([], ...(ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []));
|
|
435
|
-
organization = input(null, ...(ngDevMode ? [{ debugName: "organization" }] : /* istanbul ignore next */ []));
|
|
436
464
|
// Navigation & Integration Inputs
|
|
437
465
|
navigationItems = input([], ...(ngDevMode ? [{ debugName: "navigationItems" }] : /* istanbul ignore next */ []));
|
|
438
466
|
applications = input([], ...(ngDevMode ? [{ debugName: "applications" }] : /* istanbul ignore next */ []));
|
|
@@ -445,10 +473,8 @@ class HeaderComponent {
|
|
|
445
473
|
showNotifications = input(true, ...(ngDevMode ? [{ debugName: "showNotifications" }] : /* istanbul ignore next */ []));
|
|
446
474
|
showAppSwitcher = input(true, ...(ngDevMode ? [{ debugName: "showAppSwitcher" }] : /* istanbul ignore next */ []));
|
|
447
475
|
showThemeSwitcher = input(true, ...(ngDevMode ? [{ debugName: "showThemeSwitcher" }] : /* istanbul ignore next */ []));
|
|
448
|
-
showSignIn = input(true, ...(ngDevMode ? [{ debugName: "showSignIn" }] : /* istanbul ignore next */ []));
|
|
449
476
|
// Outputs as decided in the architecture plan
|
|
450
477
|
search = output();
|
|
451
|
-
signIn = output();
|
|
452
478
|
applicationSelected = output();
|
|
453
479
|
notificationClick = output();
|
|
454
480
|
markRead = output();
|
|
@@ -520,7 +546,7 @@ class HeaderComponent {
|
|
|
520
546
|
this.menuAction.emit(action);
|
|
521
547
|
}
|
|
522
548
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: HeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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 class=\"header-container\">\n\n <!-- Left Side: Brand, Current App & Desktop Links -->\n <div class=\"header-left\">\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=\"header-middle\">\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=\"header-spacer\"></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{position:fixed;z-index:40;top:0;right:0;left:0;display:flex;height:3.75rem;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);color:var(--foreground);background:var(--background);box-shadow:0 1px 2px #0f172a0a;padding-inline:1rem;will-change:transform}.header-left{display:flex;min-width:0;align-items:center;gap:.75rem}.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{display:none;align-items:center;gap:1.25rem;margin-left:.25rem;padding:0;background:transparent}.header-middle{display:none;flex:1 1 auto;justify-content:center;padding-inline:1rem}.header-spacer{display:none;flex:1 1 auto}.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,svg{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){.header-container{padding-inline:1.25rem}.brand-name{display:inline-block}.header-actions{gap:.25rem}.user-menu-divider{padding-left:.5rem}}@media(min-width:768px){.header-middle{display:flex}.header-spacer{display:block}.mobile-search-action{display:none}}@media(min-width:1024px){.header-container{padding-inline:2rem}.desktop-nav{display:flex}.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"] }] });
|
|
549
|
+
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: true, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", 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 } }, outputs: { search: "search", applicationSelected: "applicationSelected", notificationClick: "notificationClick", markRead: "markRead", clearAll: "clearAll", menuAction: "menuAction" }, ngImport: i0, template: "<header class=\"header-container\">\r\n\r\n <!-- Left Side: Brand, Current App & Desktop Links -->\r\n <div class=\"header-left\">\r\n\r\n <a routerLink=\"/\" class=\"brand-identity\">\r\n <img [src]=\"logoUrl()\" [alt]=\"appName() + ' logo'\" width=\"26\" height=\"26\" class=\"brand-logo\" />\r\n <span class=\"brand-name\">{{ appName() }}</span>\r\n </a>\r\n\r\n @if (currentAppName()) {\r\n <div class=\"current-app-identity\" [attr.aria-label]=\"'Current app: ' + currentAppName()\">\r\n <span class=\"brand-separator\" aria-hidden=\"true\">|</span>\r\n <span class=\"current-app-name\">{{ currentAppName() }}</span>\r\n </div>\r\n }\r\n\r\n <!-- Desktop Navigation Links -->\r\n <nav class=\"desktop-nav ml-1 hidden items-center lg:flex\" aria-label=\"Desktop primary navigation\">\r\n @for (item of navigationItems(); track item.id) {\r\n @if (hasPermission(item)) {\r\n @if (item.route) {\r\n <a [routerLink]=\"item.route\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{exact: true}\" class=\"nav-link\"\r\n [attr.aria-current]=\"isActive(item) ? 'page' : null\">\r\n {{ item.label }}\r\n </a>\r\n } @else {\r\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" class=\"nav-link\">\r\n {{ item.label }}\r\n </a>\r\n }\r\n }\r\n }\r\n </nav>\r\n </div>\r\n\r\n <!-- Middle: Search Input (Desktop) -->\r\n @if (showSearch()) {\r\n <div class=\"header-middle\">\r\n <div class=\"header-search-shell\">\r\n <lib-icon name=\"search\" [size]=\"14\"\r\n class=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--secondary)]\" />\r\n\r\n <input type=\"search\" placeholder=\"Search Excellore...\" #searchInput\r\n (keyup.enter)=\"onSearch(searchInput.value); searchInput.value = ''\" class=\"header-search\" autocomplete=\"off\"\r\n aria-label=\"Search\" />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"header-spacer\"></div>\r\n }\r\n\r\n <!-- Right Side Actions -->\r\n <div class=\"header-actions\">\r\n\r\n <!-- Mobile Search Trigger -->\r\n @if (showSearch()) {\r\n <button type=\"button\" class=\"header-action mobile-search-action\" (click)=\"toggleMobileMenu()\"\r\n aria-label=\"Open mobile menu\" aria-expanded=\"isMobileMenuOpen()\" #menuToggle>\r\n <lib-icon name=\"search\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\r\n </button>\r\n }\r\n\r\n <!-- Theme Switcher -->\r\n @if (showThemeSwitcher()) {\r\n <button type=\"button\" class=\"header-action\" (click)=\"themeService.toggleTheme()\" aria-label=\"Toggle theme\">\r\n @switch (themeService.theme()) {\r\n @case ('light') {\r\n <lib-icon name=\"sun\" [size]=\"16\" class=\"text-[var(--primary)]\" />\r\n }\r\n @case ('dark') {\r\n <lib-icon name=\"moon\" [size]=\"16\" class=\"text-[var(--primary)]\" />\r\n }\r\n @default {\r\n <lib-icon name=\"monitor\" [size]=\"16\" class=\"text-[var(--secondary)]\" />\r\n }\r\n }\r\n </button>\r\n }\r\n\r\n <!-- App Switcher -->\r\n @if (showAppSwitcher() && applications().length > 0) {\r\n <lib-app-switcher [applications]=\"applications()\" (applicationSelected)=\"onApplicationSelect($event)\" />\r\n }\r\n\r\n <!-- Notifications -->\r\n @if (showNotifications()) {\r\n <lib-notification-bell [unreadCount]=\"unreadCount()\" [notifications]=\"notifications()\"\r\n (notificationClick)=\"onNotificationClick($event)\" (markRead)=\"onMarkRead($event)\" (clearAll)=\"onClearAll()\" />\r\n }\r\n\r\n <!-- User Menu -->\n <div class=\"user-menu-divider\">\n <lib-user-menu [user]=\"user()\" (menuAction)=\"onMenuAction($event)\" />\n </div>\n\n <!-- Mobile Menu Toggle (Hamburger) -->\n @if (hasMobileMenuContent()) {\r\n <button type=\"button\" class=\"header-action mobile-menu-action\" (click)=\"toggleMobileMenu()\"\r\n aria-label=\"Toggle navigation menu\" aria-expanded=\"isMobileMenuOpen()\" #mobileMenuToggle>\r\n <lib-icon [name]=\"isMobileMenuOpen() ? 'x' : 'menu'\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\r\n </button>\r\n }\r\n </div>\r\n</header>\r\n\r\n<!-- Mobile Menu Backdrop -->\r\n@if (isMobileMenuOpen() && hasMobileMenuContent()) {\r\n<div class=\"mobile-backdrop fixed inset-0 z-30 lg:hidden\" (click)=\"closeMobileMenu()\"\r\n (keydown.escape)=\"closeMobileMenu()\" tabindex=\"-1\" aria-hidden=\"true\"></div>\r\n\r\n<!-- Mobile Navigation Drawer -->\r\n<div\r\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\"\r\n role=\"dialog\" aria-modal=\"true\" aria-label=\"Mobile navigation\" (keydown.escape)=\"closeMobileMenu()\">\r\n <!-- Mobile Search -->\r\n @if (showSearch()) {\r\n <div class=\"relative mb-3 w-full\">\r\n <lib-icon name=\"search\" [size]=\"16\"\r\n class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--secondary)] pointer-events-none\"\r\n aria-hidden=\"true\" />\r\n\r\n <input type=\"search\" placeholder=\"Search Excellore...\" #mobileSearchInput\r\n (keyup.enter)=\"onSearch(mobileSearchInput.value); mobileSearchInput.value = ''; closeMobileMenu()\"\r\n class=\"header-search mobile\" autocomplete=\"off\" aria-label=\"Search\" />\r\n </div>\r\n }\r\n\r\n <!-- Mobile Nav Links -->\r\n <nav class=\"flex flex-col gap-1 overflow-y-auto\" role=\"navigation\">\r\n @for (item of navigationItems(); track item.id) {\r\n @if (hasPermission(item)) {\r\n @if (item.route) {\r\n <a [routerLink]=\"item.route\" routerLinkActive=\"mobile-active\" [routerLinkActiveOptions]=\"{exact: true}\"\r\n (click)=\"closeMobileMenu()\" class=\"mobile-nav-link\" [attr.aria-current]=\"isActive(item) ? 'page' : null\">\r\n {{ item.label }}\r\n </a>\r\n } @else {\r\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" (click)=\"closeMobileMenu()\" class=\"mobile-nav-link\">\r\n {{ item.label }}\r\n </a>\r\n }\r\n }\r\n }\r\n </nav>\r\n</div>\r\n}\r\n", styles: [":host{display:block}.header-container{position:fixed;z-index:40;top:0;right:0;left:0;display:flex;height:3.75rem;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);color:var(--foreground);background:var(--background);box-shadow:0 1px 2px #0f172a0a;padding-inline:1rem;will-change:transform}.header-left{display:flex;min-width:0;align-items:center;gap:.75rem}.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{display:none;align-items:center;gap:1.25rem;margin-left:.25rem;padding:0;background:transparent}.header-middle{display:none;flex:1 1 auto;justify-content:center;padding-inline:1rem}.header-spacer{display:none;flex:1 1 auto}.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}.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,svg{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){.header-container{padding-inline:1.25rem}.brand-name{display:inline-block}.header-actions{gap:.25rem}.user-menu-divider{padding-left:.5rem}}@media(min-width:768px){.header-middle{display:flex}.header-spacer{display:block}.mobile-search-action{display:none}}@media(min-width:1024px){.header-container{padding-inline:1.5rem}.desktop-nav{display:flex}.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"] }] });
|
|
524
550
|
}
|
|
525
551
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: HeaderComponent, decorators: [{
|
|
526
552
|
type: Component,
|
|
@@ -532,8 +558,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
532
558
|
NotificationBellComponent,
|
|
533
559
|
UserMenuComponent,
|
|
534
560
|
IconComponent,
|
|
535
|
-
], template: "<header class=\"header-container\">\n\n <!-- Left Side: Brand, Current App & Desktop Links -->\n <div class=\"header-left\">\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=\"header-middle\">\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=\"header-spacer\"></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{position:fixed;z-index:40;top:0;right:0;left:0;display:flex;height:3.75rem;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);color:var(--foreground);background:var(--background);box-shadow:0 1px 2px #0f172a0a;padding-inline:1rem;will-change:transform}.header-left{display:flex;min-width:0;align-items:center;gap:.75rem}.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{display:none;align-items:center;gap:1.25rem;margin-left:.25rem;padding:0;background:transparent}.header-middle{display:none;flex:1 1 auto;justify-content:center;padding-inline:1rem}.header-spacer{display:none;flex:1 1 auto}.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,svg{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){.header-container{padding-inline:1.25rem}.brand-name{display:inline-block}.header-actions{gap:.25rem}.user-menu-divider{padding-left:.5rem}}@media(min-width:768px){.header-middle{display:flex}.header-spacer{display:block}.mobile-search-action{display:none}}@media(min-width:1024px){.header-container{padding-inline:2rem}.desktop-nav{display:flex}.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:
|
|
561
|
+
], template: "<header class=\"header-container\">\r\n\r\n <!-- Left Side: Brand, Current App & Desktop Links -->\r\n <div class=\"header-left\">\r\n\r\n <a routerLink=\"/\" class=\"brand-identity\">\r\n <img [src]=\"logoUrl()\" [alt]=\"appName() + ' logo'\" width=\"26\" height=\"26\" class=\"brand-logo\" />\r\n <span class=\"brand-name\">{{ appName() }}</span>\r\n </a>\r\n\r\n @if (currentAppName()) {\r\n <div class=\"current-app-identity\" [attr.aria-label]=\"'Current app: ' + currentAppName()\">\r\n <span class=\"brand-separator\" aria-hidden=\"true\">|</span>\r\n <span class=\"current-app-name\">{{ currentAppName() }}</span>\r\n </div>\r\n }\r\n\r\n <!-- Desktop Navigation Links -->\r\n <nav class=\"desktop-nav ml-1 hidden items-center lg:flex\" aria-label=\"Desktop primary navigation\">\r\n @for (item of navigationItems(); track item.id) {\r\n @if (hasPermission(item)) {\r\n @if (item.route) {\r\n <a [routerLink]=\"item.route\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{exact: true}\" class=\"nav-link\"\r\n [attr.aria-current]=\"isActive(item) ? 'page' : null\">\r\n {{ item.label }}\r\n </a>\r\n } @else {\r\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" class=\"nav-link\">\r\n {{ item.label }}\r\n </a>\r\n }\r\n }\r\n }\r\n </nav>\r\n </div>\r\n\r\n <!-- Middle: Search Input (Desktop) -->\r\n @if (showSearch()) {\r\n <div class=\"header-middle\">\r\n <div class=\"header-search-shell\">\r\n <lib-icon name=\"search\" [size]=\"14\"\r\n class=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--secondary)]\" />\r\n\r\n <input type=\"search\" placeholder=\"Search Excellore...\" #searchInput\r\n (keyup.enter)=\"onSearch(searchInput.value); searchInput.value = ''\" class=\"header-search\" autocomplete=\"off\"\r\n aria-label=\"Search\" />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"header-spacer\"></div>\r\n }\r\n\r\n <!-- Right Side Actions -->\r\n <div class=\"header-actions\">\r\n\r\n <!-- Mobile Search Trigger -->\r\n @if (showSearch()) {\r\n <button type=\"button\" class=\"header-action mobile-search-action\" (click)=\"toggleMobileMenu()\"\r\n aria-label=\"Open mobile menu\" aria-expanded=\"isMobileMenuOpen()\" #menuToggle>\r\n <lib-icon name=\"search\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\r\n </button>\r\n }\r\n\r\n <!-- Theme Switcher -->\r\n @if (showThemeSwitcher()) {\r\n <button type=\"button\" class=\"header-action\" (click)=\"themeService.toggleTheme()\" aria-label=\"Toggle theme\">\r\n @switch (themeService.theme()) {\r\n @case ('light') {\r\n <lib-icon name=\"sun\" [size]=\"16\" class=\"text-[var(--primary)]\" />\r\n }\r\n @case ('dark') {\r\n <lib-icon name=\"moon\" [size]=\"16\" class=\"text-[var(--primary)]\" />\r\n }\r\n @default {\r\n <lib-icon name=\"monitor\" [size]=\"16\" class=\"text-[var(--secondary)]\" />\r\n }\r\n }\r\n </button>\r\n }\r\n\r\n <!-- App Switcher -->\r\n @if (showAppSwitcher() && applications().length > 0) {\r\n <lib-app-switcher [applications]=\"applications()\" (applicationSelected)=\"onApplicationSelect($event)\" />\r\n }\r\n\r\n <!-- Notifications -->\r\n @if (showNotifications()) {\r\n <lib-notification-bell [unreadCount]=\"unreadCount()\" [notifications]=\"notifications()\"\r\n (notificationClick)=\"onNotificationClick($event)\" (markRead)=\"onMarkRead($event)\" (clearAll)=\"onClearAll()\" />\r\n }\r\n\r\n <!-- User Menu -->\n <div class=\"user-menu-divider\">\n <lib-user-menu [user]=\"user()\" (menuAction)=\"onMenuAction($event)\" />\n </div>\n\n <!-- Mobile Menu Toggle (Hamburger) -->\n @if (hasMobileMenuContent()) {\r\n <button type=\"button\" class=\"header-action mobile-menu-action\" (click)=\"toggleMobileMenu()\"\r\n aria-label=\"Toggle navigation menu\" aria-expanded=\"isMobileMenuOpen()\" #mobileMenuToggle>\r\n <lib-icon [name]=\"isMobileMenuOpen() ? 'x' : 'menu'\" [size]=\"16\" class=\"h-4.5 w-4.5\" aria-hidden=\"true\" />\r\n </button>\r\n }\r\n </div>\r\n</header>\r\n\r\n<!-- Mobile Menu Backdrop -->\r\n@if (isMobileMenuOpen() && hasMobileMenuContent()) {\r\n<div class=\"mobile-backdrop fixed inset-0 z-30 lg:hidden\" (click)=\"closeMobileMenu()\"\r\n (keydown.escape)=\"closeMobileMenu()\" tabindex=\"-1\" aria-hidden=\"true\"></div>\r\n\r\n<!-- Mobile Navigation Drawer -->\r\n<div\r\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\"\r\n role=\"dialog\" aria-modal=\"true\" aria-label=\"Mobile navigation\" (keydown.escape)=\"closeMobileMenu()\">\r\n <!-- Mobile Search -->\r\n @if (showSearch()) {\r\n <div class=\"relative mb-3 w-full\">\r\n <lib-icon name=\"search\" [size]=\"16\"\r\n class=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--secondary)] pointer-events-none\"\r\n aria-hidden=\"true\" />\r\n\r\n <input type=\"search\" placeholder=\"Search Excellore...\" #mobileSearchInput\r\n (keyup.enter)=\"onSearch(mobileSearchInput.value); mobileSearchInput.value = ''; closeMobileMenu()\"\r\n class=\"header-search mobile\" autocomplete=\"off\" aria-label=\"Search\" />\r\n </div>\r\n }\r\n\r\n <!-- Mobile Nav Links -->\r\n <nav class=\"flex flex-col gap-1 overflow-y-auto\" role=\"navigation\">\r\n @for (item of navigationItems(); track item.id) {\r\n @if (hasPermission(item)) {\r\n @if (item.route) {\r\n <a [routerLink]=\"item.route\" routerLinkActive=\"mobile-active\" [routerLinkActiveOptions]=\"{exact: true}\"\r\n (click)=\"closeMobileMenu()\" class=\"mobile-nav-link\" [attr.aria-current]=\"isActive(item) ? 'page' : null\">\r\n {{ item.label }}\r\n </a>\r\n } @else {\r\n <a [href]=\"item.href\" [target]=\"item.target || '_self'\" (click)=\"closeMobileMenu()\" class=\"mobile-nav-link\">\r\n {{ item.label }}\r\n </a>\r\n }\r\n }\r\n }\r\n </nav>\r\n</div>\r\n}\r\n", styles: [":host{display:block}.header-container{position:fixed;z-index:40;top:0;right:0;left:0;display:flex;height:3.75rem;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);color:var(--foreground);background:var(--background);box-shadow:0 1px 2px #0f172a0a;padding-inline:1rem;will-change:transform}.header-left{display:flex;min-width:0;align-items:center;gap:.75rem}.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{display:none;align-items:center;gap:1.25rem;margin-left:.25rem;padding:0;background:transparent}.header-middle{display:none;flex:1 1 auto;justify-content:center;padding-inline:1rem}.header-spacer{display:none;flex:1 1 auto}.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}.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,svg{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){.header-container{padding-inline:1.25rem}.brand-name{display:inline-block}.header-actions{gap:.25rem}.user-menu-divider{padding-left:.5rem}}@media(min-width:768px){.header-middle{display:flex}.header-spacer{display:block}.mobile-search-action{display:none}}@media(min-width:1024px){.header-container{padding-inline:1.5rem}.desktop-nav{display:flex}.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"] }]
|
|
562
|
+
}], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: true }] }], permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", 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 }] }], 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"] }] } });
|
|
563
|
+
|
|
564
|
+
class SidebarComponent {
|
|
565
|
+
menuItems = input([], ...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
|
|
566
|
+
menuError = input(null, ...(ngDevMode ? [{ debugName: "menuError" }] : /* istanbul ignore next */ []));
|
|
567
|
+
showMobileToggle = input(true, ...(ngDevMode ? [{ debugName: "showMobileToggle" }] : /* istanbul ignore next */ []));
|
|
568
|
+
menuItemSelected = output();
|
|
569
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
570
|
+
toggleSidebar() {
|
|
571
|
+
this.isOpen.update((value) => !value);
|
|
572
|
+
}
|
|
573
|
+
closeSidebar() {
|
|
574
|
+
this.isOpen.set(false);
|
|
575
|
+
}
|
|
576
|
+
onMenuItemSelected(item) {
|
|
577
|
+
this.menuItemSelected.emit(item);
|
|
578
|
+
this.closeSidebar();
|
|
579
|
+
}
|
|
580
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: SidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
581
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: SidebarComponent, isStandalone: true, selector: "lib-sidebar", inputs: { menuItems: { classPropertyName: "menuItems", publicName: "menuItems", isSignal: true, isRequired: false, transformFunction: null }, menuError: { classPropertyName: "menuError", publicName: "menuError", isSignal: true, isRequired: false, transformFunction: null }, showMobileToggle: { classPropertyName: "showMobileToggle", publicName: "showMobileToggle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuItemSelected: "menuItemSelected" }, ngImport: i0, template: "@if (showMobileToggle()) {\r\n<button\r\n class=\"sidebar-toggle\"\r\n type=\"button\"\r\n title=\"Toggle sidebar menu\"\r\n aria-label=\"Toggle sidebar menu\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (click)=\"toggleSidebar()\"\r\n>\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n</button>\r\n}\r\n\r\n@if (isOpen()) {\r\n<div class=\"sidebar-overlay\" (click)=\"closeSidebar()\" (keydown.escape)=\"closeSidebar()\" tabindex=\"-1\" aria-hidden=\"true\"></div>\r\n}\r\n\r\n<aside class=\"sidebar\" [class.open]=\"isOpen()\" aria-label=\"Application sidebar\">\n <div class=\"sidebar__middle\">\n <lib-sidebar-menu\n [items]=\"menuItems()\"\n [errorMessage]=\"menuError()\"\n (menuItemSelected)=\"onMenuItemSelected($event)\"\n />\n </div>\n</aside>\n", styles: [":host{display:block}.sidebar-toggle{position:fixed;z-index:50;top:.75rem;left:.75rem;display:grid;gap:.25rem;width:2.5rem;height:2.5rem;place-content:center;border:1px solid var(--border, #e5e7eb);border-radius:.5rem;background:var(--background, #ffffff);box-shadow:0 1px 2px #0f172a14;cursor:pointer}.sidebar-toggle span{display:block;width:1.125rem;height:.125rem;border-radius:999px;background:var(--accent-primary, var(--primary, #0067b8))}.sidebar-overlay{position:fixed;z-index:40;inset:0;background:#0000004d}.sidebar{position:fixed;z-index:45;top:0;left:0;display:flex;width:17.1875rem;height:100dvh;flex-direction:column;overflow:hidden;border-right:1px solid var(--border-primary, var(--border, #e5e7eb));background:var(--bg-primary, var(--background, #ffffff));color:var(--text-primary, var(--foreground, #111827));transform:translate(-100%);transition:transform .3s ease-in-out}.sidebar.open{transform:translate(0)}.sidebar__middle{min-height:0;flex:1 1 auto;overflow:hidden}@media(min-width:768px){.sidebar-toggle,.sidebar-overlay{display:none}.sidebar{position:sticky;z-index:auto;transform:translate(0)}}\n"], dependencies: [{ kind: "component", type: SidebarMenuComponent, selector: "lib-sidebar-menu", inputs: ["items", "errorMessage", "defaultIcon"], outputs: ["menuItemSelected"] }] });
|
|
582
|
+
}
|
|
583
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: SidebarComponent, decorators: [{
|
|
584
|
+
type: Component,
|
|
585
|
+
args: [{ selector: 'lib-sidebar', standalone: true, imports: [SidebarMenuComponent], template: "@if (showMobileToggle()) {\r\n<button\r\n class=\"sidebar-toggle\"\r\n type=\"button\"\r\n title=\"Toggle sidebar menu\"\r\n aria-label=\"Toggle sidebar menu\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (click)=\"toggleSidebar()\"\r\n>\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n</button>\r\n}\r\n\r\n@if (isOpen()) {\r\n<div class=\"sidebar-overlay\" (click)=\"closeSidebar()\" (keydown.escape)=\"closeSidebar()\" tabindex=\"-1\" aria-hidden=\"true\"></div>\r\n}\r\n\r\n<aside class=\"sidebar\" [class.open]=\"isOpen()\" aria-label=\"Application sidebar\">\n <div class=\"sidebar__middle\">\n <lib-sidebar-menu\n [items]=\"menuItems()\"\n [errorMessage]=\"menuError()\"\n (menuItemSelected)=\"onMenuItemSelected($event)\"\n />\n </div>\n</aside>\n", styles: [":host{display:block}.sidebar-toggle{position:fixed;z-index:50;top:.75rem;left:.75rem;display:grid;gap:.25rem;width:2.5rem;height:2.5rem;place-content:center;border:1px solid var(--border, #e5e7eb);border-radius:.5rem;background:var(--background, #ffffff);box-shadow:0 1px 2px #0f172a14;cursor:pointer}.sidebar-toggle span{display:block;width:1.125rem;height:.125rem;border-radius:999px;background:var(--accent-primary, var(--primary, #0067b8))}.sidebar-overlay{position:fixed;z-index:40;inset:0;background:#0000004d}.sidebar{position:fixed;z-index:45;top:0;left:0;display:flex;width:17.1875rem;height:100dvh;flex-direction:column;overflow:hidden;border-right:1px solid var(--border-primary, var(--border, #e5e7eb));background:var(--bg-primary, var(--background, #ffffff));color:var(--text-primary, var(--foreground, #111827));transform:translate(-100%);transition:transform .3s ease-in-out}.sidebar.open{transform:translate(0)}.sidebar__middle{min-height:0;flex:1 1 auto;overflow:hidden}@media(min-width:768px){.sidebar-toggle,.sidebar-overlay{display:none}.sidebar{position:sticky;z-index:auto;transform:translate(0)}}\n"] }]
|
|
586
|
+
}], propDecorators: { menuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuItems", required: false }] }], menuError: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuError", required: false }] }], showMobileToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMobileToggle", required: false }] }], menuItemSelected: [{ type: i0.Output, args: ["menuItemSelected"] }] } });
|
|
537
587
|
|
|
538
588
|
/*
|
|
539
589
|
* Public API Surface of ui
|
|
@@ -543,5 +593,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
543
593
|
* Generated bundle index. Do not edit.
|
|
544
594
|
*/
|
|
545
595
|
|
|
546
|
-
export {
|
|
596
|
+
export { HeaderComponent, SidebarComponent, ThemeService };
|
|
547
597
|
//# sourceMappingURL=excellore-ui.mjs.map
|