@styloviz/layout 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 StyloViz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @styloviz/layout
2
+
3
+ > Responsive dashboard layout primitives — app shell, sidebar, header and content grid.
4
+
5
+ Part of the **StyloViz UI Kit** — a premium Angular 21 + Tailwind CSS 4 dashboard component library. Standalone, `OnPush`, strongly typed, dark-mode ready.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @styloviz/core @styloviz/layout
11
+ ```
12
+
13
+ Requires `@angular/core` and `@angular/common` >= 21. `@styloviz/core` is a peer dependency shared by every component. Prefer everything at once? `@styloviz/all` installs the whole free tier in one command.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { SvDashboardShellComponent } from '@styloviz/layout';
19
+
20
+ @Component({
21
+ standalone: true,
22
+ imports: [SvDashboardShellComponent],
23
+ template: `
24
+ <sv-dashboard-shell />
25
+ `,
26
+ })
27
+ export class DemoComponent {}
28
+ ```
29
+
30
+ ## Documentation
31
+
32
+ Full API reference and live demos: https://styloviz.dev/docs
33
+
34
+ ## License
35
+
36
+ MIT
@@ -0,0 +1,294 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, output, inject, ElementRef, effect, computed, ChangeDetectionStrategy, Component, signal } from '@angular/core';
3
+ import { toSignal } from '@angular/core/rxjs-interop';
4
+ import { Router, NavigationEnd, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
5
+ import { filter, map, startWith } from 'rxjs';
6
+ import { ThemeService } from '@styloviz/core';
7
+ import { SvToastHostComponent } from '@styloviz/toast';
8
+
9
+ /**
10
+ * SidebarComponent
11
+ *
12
+ * Collapsible navigation sidebar with grouped menu items.
13
+ *
14
+ * Features:
15
+ * - Expanded mode: shows icons + labels + group headings
16
+ * - Collapsed mode: shows icons only with tooltip-style labels
17
+ * - Mobile mode: slides in as overlay from the left
18
+ * - Active route highlighting via RouterLinkActive
19
+ * - Accessible keyboard navigation
20
+ */
21
+ class SvSidebarComponent {
22
+ /** Whether the sidebar is in collapsed (icon-only) mode */
23
+ collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
24
+ /** Whether the mobile sidebar overlay is open */
25
+ mobileOpen = input(false, ...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
26
+ /** Emits when the mobile sidebar should close */
27
+ mobileClose = output();
28
+ /** Emits when the user clicks the collapse/expand toggle inside the sidebar */
29
+ toggleCollapse = output();
30
+ /**
31
+ * Navigation menu groups injected by the parent shell.
32
+ * Defaults to the built-in NAVIGATION_ITEMS so the component
33
+ * works out-of-the-box, but buyers can pass their own structure.
34
+ */
35
+ menuGroups = input([], ...(ngDevMode ? [{ debugName: "menuGroups" }] : /* istanbul ignore next */ []));
36
+ // ── Footer resource links (override to point at your own URLs) ───────────
37
+ /** Documentation site URL. */
38
+ docsUrl = input('#docs', ...(ngDevMode ? [{ debugName: "docsUrl" }] : /* istanbul ignore next */ []));
39
+ /** GitHub repository URL. */
40
+ githubUrl = input('#github', ...(ngDevMode ? [{ debugName: "githubUrl" }] : /* istanbul ignore next */ []));
41
+ /** npm package URL. */
42
+ npmUrl = input('#npm', ...(ngDevMode ? [{ debugName: "npmUrl" }] : /* istanbul ignore next */ []));
43
+ /** Community / forum URL. */
44
+ communityUrl = input('#community', ...(ngDevMode ? [{ debugName: "communityUrl" }] : /* istanbul ignore next */ []));
45
+ // ── Router-aware sub-navigation ─────────────────────────────────────────
46
+ router = inject(Router);
47
+ /** Host element — used to locate and center the active nav item. */
48
+ host = inject(ElementRef);
49
+ constructor() {
50
+ // Keep the selected navigation item centered in the sidebar viewport
51
+ // whenever the selection changes (and on initial load).
52
+ effect(() => {
53
+ const id = this.activeChildId();
54
+ if (!id || typeof requestAnimationFrame === 'undefined')
55
+ return;
56
+ // Defer to the next frame so the active item (and its expanded
57
+ // sub-navigation) are present in the DOM before we measure/scroll.
58
+ requestAnimationFrame(() => {
59
+ const el = this.host.nativeElement.querySelector(`[data-nav-item="${CSS.escape(id)}"]`);
60
+ if (!el)
61
+ return;
62
+ const reduceMotion = typeof matchMedia !== 'undefined' &&
63
+ matchMedia('(prefers-reduced-motion: reduce)').matches;
64
+ el.scrollIntoView({ block: 'center', behavior: reduceMotion ? 'auto' : 'smooth' });
65
+ });
66
+ });
67
+ }
68
+ /** Reactive snapshot of the current URL, updated on every navigation. */
69
+ routerUrl = toSignal(this.router.events.pipe(filter(e => e instanceof NavigationEnd), map(() => this.router.url), startWith(this.router.url)), { initialValue: this.router.url });
70
+ /**
71
+ * ID of the active child item (read from ?c= query param), or `null` on a
72
+ * bare `/components` route — which shows the dashboard overview, so no child
73
+ * item should appear selected.
74
+ */
75
+ activeChildId = computed(() => {
76
+ const match = this.routerUrl().match(/[?&]c=([^&]+)/);
77
+ return match ? decodeURIComponent(match[1]) : null;
78
+ }, ...(ngDevMode ? [{ debugName: "activeChildId" }] : /* istanbul ignore next */ []));
79
+ /** True when the current route starts with the given path. */
80
+ isOnRoute(route) {
81
+ return this.routerUrl().startsWith(route);
82
+ }
83
+ /** Tailwind class string for a sidebar child nav item. */
84
+ childItemClass(id) {
85
+ const base = 'flex w-full items-center rounded-md py-[5px] pl-3 pr-2 text-[12.5px] font-medium transition-colors duration-100';
86
+ if (this.activeChildId() === id) {
87
+ return `${base} bg-primary-50 text-primary-700 dark:bg-primary-950/70 dark:text-primary-300`;
88
+ }
89
+ return `${base} text-gray-500 hover:bg-gray-100/60 hover:text-gray-800 dark:text-gray-500 dark:hover:bg-gray-800/50 dark:hover:text-gray-300`;
90
+ }
91
+ /**
92
+ * Returns the SVG path data for a given icon name.
93
+ *
94
+ * We use inline SVGs instead of an icon library to keep
95
+ * the kit dependency-free. Each icon is a 24x24 Heroicon outline.
96
+ */
97
+ getIconPath(icon) {
98
+ const icons = {
99
+ 'rocket': 'M9.315 7.584C12.195 3.883 16.695 1.5 21.75 1.5a.75.75 0 01.75.75c0 5.056-2.383 9.555-6.084 12.436A6.75 6.75 0 019.75 22.5a.75.75 0 01-.75-.75v-4.131A15.838 15.838 0 016.382 15H2.25a.75.75 0 01-.75-.75 6.75 6.75 0 017.815-6.666zM15 6.75a2.25 2.25 0 100 4.5 2.25 2.25 0 000-4.5zM5.26 17.242a.75.75 0 10-.897-1.203 5.243 5.243 0 00-2.05 5.022.75.75 0 00.625.627 5.243 5.243 0 005.022-2.051.75.75 0 10-1.202-.897 3.744 3.744 0 01-3.008 1.51c0-1.23.592-2.323 1.51-3.008z',
100
+ 'book': 'M11.25 4.533A9.707 9.707 0 006 3a9.735 9.735 0 00-3.25.555.75.75 0 00-.5.707v14.25a.75.75 0 001 .707A8.237 8.237 0 016 18.75c1.995 0 3.823.707 5.25 1.886V4.533zM12.75 20.636A8.214 8.214 0 0118 18.75c.966 0 1.89.166 2.75.47a.75.75 0 001-.708V4.262a.75.75 0 00-.5-.707A9.735 9.735 0 0018 3a9.707 9.707 0 00-5.25 1.533v16.103z',
101
+ 'chart-bar': 'M3 13h2v8H3zm6-4h2v12H9zm6-4h2v16h-2zm6 8h2v8h-2z',
102
+ 'users': 'M16 11c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 3-1.34 3-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z',
103
+ 'shopping-cart': 'M7 18c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM7.17 14.75l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.86-7.01L19.42 4h-.01l-1.1 2-2.76 5H8.53l-.13-.27L6.16 6l-.95-2-.94-2H1v2h2l3.6 7.59-1.35 2.44C5.52 15.37 6.48 17 8 17h12v-2H8l1.1-2h7.45z',
104
+ 'cube': 'M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.36.2-.8.2-1.14 0l-7.9-4.44A.99.99 0 013 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.36-.2.8-.2 1.14 0l7.9 4.44c.32.17.53.5.53.88v9zM12 4.15L5 8.09v7.82l7 3.94 7-3.94V8.09l-7-3.94z',
105
+ 'cog': 'M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1115.6 12 3.611 3.611 0 0112 15.6z',
106
+ };
107
+ return icons[icon] ?? '';
108
+ }
109
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
110
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvSidebarComponent, isStandalone: true, selector: "sv-sidebar", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileOpen: { classPropertyName: "mobileOpen", publicName: "mobileOpen", isSignal: true, isRequired: false, transformFunction: null }, menuGroups: { classPropertyName: "menuGroups", publicName: "menuGroups", isSignal: true, isRequired: false, transformFunction: null }, docsUrl: { classPropertyName: "docsUrl", publicName: "docsUrl", isSignal: true, isRequired: false, transformFunction: null }, githubUrl: { classPropertyName: "githubUrl", publicName: "githubUrl", isSignal: true, isRequired: false, transformFunction: null }, npmUrl: { classPropertyName: "npmUrl", publicName: "npmUrl", isSignal: true, isRequired: false, transformFunction: null }, communityUrl: { classPropertyName: "communityUrl", publicName: "communityUrl", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mobileClose: "mobileClose", toggleCollapse: "toggleCollapse" }, ngImport: i0, template: "<!-- ============================================================\n Sidebar Navigation\n ============================================================\n Three states:\n 1. Desktop expanded \u2014 full width with labels\n 2. Desktop collapsed \u2014 narrow with icons only\n 3. Mobile overlay \u2014 slides in from left\n ============================================================ -->\n\n<aside\n class=\"fixed top-0 left-0 z-50 flex h-full flex-col border-r border-white/70 bg-white/78\n shadow-[0_24px_60px_-36px_rgba(15,23,42,0.85)] backdrop-blur-xl transition-all duration-300\n dark:border-gray-700/45 dark:bg-gray-900/70 dark:shadow-[0_26px_64px_-38px_rgba(2,6,23,0.95)]\"\n [class.w-64]=\"!collapsed()\"\n [class.w-20]=\"collapsed()\"\n [class.-translate-x-full]=\"!mobileOpen()\"\n [class.translate-x-0]=\"mobileOpen()\"\n [class.lg:translate-x-0]=\"true\"\n role=\"navigation\"\n aria-label=\"Main navigation\">\n\n <!-- ========== Logo Area (matches topbar height) ========== -->\n <div class=\"flex h-16 shrink-0 items-center border-b border-gray-200/80 px-3 dark:border-gray-800/60\"\n [class.justify-center]=\"collapsed()\">\n\n @if (!collapsed()) {\n <!-- Brand mark + wordmark -->\n <a routerLink=\"/components\" class=\"flex items-center gap-2.5\" aria-label=\"styloviz home\">\n <img src=\"logo-icon-sm.svg\" alt=\"styloviz logo\" class=\"h-8 w-auto shrink-0\" />\n <span class=\"text-[22px] font-semibold lowercase leading-none tracking-tight text-gray-900 dark:text-gray-100\">\n styloviz\n </span>\n </a>\n } @else {\n <!-- Brand mark only when collapsed -->\n <a routerLink=\"/components\" aria-label=\"styloviz home\" title=\"styloviz\">\n <img src=\"logo-icon-sm.svg\" alt=\"styloviz logo\" class=\"h-8 w-auto shrink-0\" />\n </a>\n }\n </div>\n\n <!-- ========== Navigation Menu ========== -->\n <nav class=\"flex-1 overflow-y-auto px-2.5 py-3\">\n @for (group of menuGroups(); track group.groupLabel) {\n <!-- Group Label (hidden when collapsed) -->\n @if (!collapsed()) {\n <p class=\"mb-1 mt-4 px-2.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400/80 dark:text-gray-600\">\n {{ group.groupLabel }}\n </p>\n } @else {\n <!-- Divider between groups in collapsed mode -->\n <div class=\"my-2.5 border-t border-gray-200/80 dark:border-gray-800/60\"></div>\n }\n\n <!-- Menu Items -->\n @for (item of group.items; track item.route) {\n <a\n [routerLink]=\"item.route\"\n routerLinkActive=\"bg-primary-50 text-primary-700 font-semibold dark:bg-primary-950/70 dark:text-primary-300\"\n [routerLinkActiveOptions]=\"{ exact: false }\"\n class=\"group mb-0.5 flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-[13.5px] font-medium\n text-gray-600 transition-colors duration-100\n hover:bg-gray-100/80 hover:text-gray-900\n dark:text-gray-400 dark:hover:bg-gray-800/70 dark:hover:text-gray-100\"\n [class.justify-center]=\"collapsed()\"\n [attr.title]=\"collapsed() ? item.label : null\"\n (click)=\"mobileClose.emit()\">\n\n <!-- Icon -->\n <svg\n class=\"h-[18px] w-[18px] shrink-0 opacity-80\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path [attr.d]=\"getIconPath(item.icon)\" />\n </svg>\n\n <!-- Label (hidden when collapsed) -->\n @if (!collapsed()) {\n <span class=\"flex-1 tracking-tight\">{{ item.label }}</span>\n\n <!-- Badge -->\n @if (item.badge) {\n <span class=\"rounded-full bg-primary-100 px-1.5 py-0.5 text-[10px] font-semibold text-primary-700\n dark:bg-primary-900/50 dark:text-primary-300\">\n {{ item.badge }}\n </span>\n }\n }\n </a>\n\n <!-- \u2500\u2500 Sub-navigation (shown when expanded and on this route) \u2500\u2500 -->\n @if (!collapsed() && item.childGroups && isOnRoute(item.route)) {\n <div class=\"mb-1 ml-[1.125rem] border-l border-gray-200/70 pl-3 dark:border-gray-800/50\">\n @for (childGroup of item.childGroups; track childGroup.label) {\n <!-- Sub-group label -->\n <p class=\"mb-0.5 px-1 text-[9.5px] font-semibold uppercase tracking-widest\n text-gray-400/70 dark:text-gray-700\"\n [class.mt-3]=\"!$first\"\n [class.mt-1.5]=\"$first\">\n {{ childGroup.label }}\n </p>\n <!-- Sub-items -->\n @for (child of childGroup.items; track child.id) {\n <a\n [routerLink]=\"[item.route]\"\n [queryParams]=\"{ c: child.id }\"\n [attr.data-nav-item]=\"child.id\"\n [class]=\"childItemClass(child.id)\"\n [attr.aria-current]=\"activeChildId() === child.id ? 'page' : null\"\n (click)=\"mobileClose.emit()\">\n {{ child.label }}\n </a>\n }\n }\n </div>\n }\n }\n }\n </nav>\n\n <!-- ========== Sidebar Footer (pinned) ========== -->\n <div class=\"shrink-0 border-t border-gray-200/80 px-2.5 py-2.5 dark:border-gray-800/60\">\n @if (!collapsed()) {\n <!-- Resources -->\n <div class=\"flex items-center justify-between rounded-lg bg-gray-50/70 px-1.5 py-1.5 dark:bg-gray-800/40\">\n <div class=\"flex items-center gap-0.5\">\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Documentation\" aria-label=\"Documentation\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M11.25 4.533A9.707 9.707 0 006 3a9.735 9.735 0 00-3.25.555.75.75 0 00-.5.707v14.25a.75.75 0 001 .707A8.237 8.237 0 016 18.75c1.995 0 3.823.707 5.25 1.886V4.533zM12.75 20.636A8.214 8.214 0 0118 18.75c.966 0 1.89.166 2.75.47a.75.75 0 001-.708V4.262a.75.75 0 00-.5-.707A9.735 9.735 0 0018 3a9.707 9.707 0 00-5.25 1.533v16.103z\" />\n </svg>\n </a>\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"GitHub\" aria-label=\"GitHub\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n <a [href]=\"npmUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"npm\" aria-label=\"npm package\"\n class=\"inline-flex h-7 items-center justify-center rounded-md px-1.5 font-mono text-[10px] font-bold\n text-gray-400 transition-colors hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n npm\n </a>\n <a [href]=\"communityUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Community\" aria-label=\"Community forum\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4.913 2.658c2.075-.27 4.19-.408 6.337-.408 2.147 0 4.262.139 6.337.408 1.922.25 3.291 1.861 3.405 3.727a4.403 4.403 0 00-1.032-.211 50.89 50.89 0 00-8.42 0c-2.358.196-4.04 2.19-4.04 4.434v4.286a4.47 4.47 0 002.433 3.984L7.28 21.53A.75.75 0 016 21v-4.03a48.527 48.527 0 01-1.087-.128C2.905 16.58 1.5 14.833 1.5 12.862V6.638c0-1.97 1.405-3.718 3.413-3.979z\" />\n <path d=\"M15.75 7.5c-1.376 0-2.739.057-4.086.169C10.124 7.797 9 9.103 9 10.609v4.285c0 1.507 1.128 2.814 2.67 2.94 1.243.102 2.5.157 3.768.165l2.782 2.781a.75.75 0 001.28-.53v-2.39l.33-.026c1.542-.125 2.67-1.433 2.67-2.94v-4.286c0-1.505-1.125-2.811-2.664-2.94A49.392 49.392 0 0015.75 7.5z\" />\n </svg>\n </a>\n </div>\n <span class=\"pr-1 text-[10px] font-semibold text-gray-400 dark:text-gray-600\">v1.0</span>\n </div>\n } @else {\n <!-- Collapsed: GitHub quick link -->\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"GitHub\" aria-label=\"GitHub\"\n class=\"flex items-center justify-center rounded-lg p-2 text-gray-400 transition-colors\n hover:bg-gray-100/80 hover:text-primary-600\n dark:text-gray-500 dark:hover:bg-gray-800/70 dark:hover:text-primary-300\">\n <svg class=\"h-[18px] w-[18px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n }\n </div>\n</aside>", dependencies: [{ 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
111
+ }
112
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvSidebarComponent, decorators: [{
113
+ type: Component,
114
+ args: [{ selector: 'sv-sidebar', standalone: true, imports: [RouterLink, RouterLinkActive], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- ============================================================\n Sidebar Navigation\n ============================================================\n Three states:\n 1. Desktop expanded \u2014 full width with labels\n 2. Desktop collapsed \u2014 narrow with icons only\n 3. Mobile overlay \u2014 slides in from left\n ============================================================ -->\n\n<aside\n class=\"fixed top-0 left-0 z-50 flex h-full flex-col border-r border-white/70 bg-white/78\n shadow-[0_24px_60px_-36px_rgba(15,23,42,0.85)] backdrop-blur-xl transition-all duration-300\n dark:border-gray-700/45 dark:bg-gray-900/70 dark:shadow-[0_26px_64px_-38px_rgba(2,6,23,0.95)]\"\n [class.w-64]=\"!collapsed()\"\n [class.w-20]=\"collapsed()\"\n [class.-translate-x-full]=\"!mobileOpen()\"\n [class.translate-x-0]=\"mobileOpen()\"\n [class.lg:translate-x-0]=\"true\"\n role=\"navigation\"\n aria-label=\"Main navigation\">\n\n <!-- ========== Logo Area (matches topbar height) ========== -->\n <div class=\"flex h-16 shrink-0 items-center border-b border-gray-200/80 px-3 dark:border-gray-800/60\"\n [class.justify-center]=\"collapsed()\">\n\n @if (!collapsed()) {\n <!-- Brand mark + wordmark -->\n <a routerLink=\"/components\" class=\"flex items-center gap-2.5\" aria-label=\"styloviz home\">\n <img src=\"logo-icon-sm.svg\" alt=\"styloviz logo\" class=\"h-8 w-auto shrink-0\" />\n <span class=\"text-[22px] font-semibold lowercase leading-none tracking-tight text-gray-900 dark:text-gray-100\">\n styloviz\n </span>\n </a>\n } @else {\n <!-- Brand mark only when collapsed -->\n <a routerLink=\"/components\" aria-label=\"styloviz home\" title=\"styloviz\">\n <img src=\"logo-icon-sm.svg\" alt=\"styloviz logo\" class=\"h-8 w-auto shrink-0\" />\n </a>\n }\n </div>\n\n <!-- ========== Navigation Menu ========== -->\n <nav class=\"flex-1 overflow-y-auto px-2.5 py-3\">\n @for (group of menuGroups(); track group.groupLabel) {\n <!-- Group Label (hidden when collapsed) -->\n @if (!collapsed()) {\n <p class=\"mb-1 mt-4 px-2.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400/80 dark:text-gray-600\">\n {{ group.groupLabel }}\n </p>\n } @else {\n <!-- Divider between groups in collapsed mode -->\n <div class=\"my-2.5 border-t border-gray-200/80 dark:border-gray-800/60\"></div>\n }\n\n <!-- Menu Items -->\n @for (item of group.items; track item.route) {\n <a\n [routerLink]=\"item.route\"\n routerLinkActive=\"bg-primary-50 text-primary-700 font-semibold dark:bg-primary-950/70 dark:text-primary-300\"\n [routerLinkActiveOptions]=\"{ exact: false }\"\n class=\"group mb-0.5 flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-[13.5px] font-medium\n text-gray-600 transition-colors duration-100\n hover:bg-gray-100/80 hover:text-gray-900\n dark:text-gray-400 dark:hover:bg-gray-800/70 dark:hover:text-gray-100\"\n [class.justify-center]=\"collapsed()\"\n [attr.title]=\"collapsed() ? item.label : null\"\n (click)=\"mobileClose.emit()\">\n\n <!-- Icon -->\n <svg\n class=\"h-[18px] w-[18px] shrink-0 opacity-80\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path [attr.d]=\"getIconPath(item.icon)\" />\n </svg>\n\n <!-- Label (hidden when collapsed) -->\n @if (!collapsed()) {\n <span class=\"flex-1 tracking-tight\">{{ item.label }}</span>\n\n <!-- Badge -->\n @if (item.badge) {\n <span class=\"rounded-full bg-primary-100 px-1.5 py-0.5 text-[10px] font-semibold text-primary-700\n dark:bg-primary-900/50 dark:text-primary-300\">\n {{ item.badge }}\n </span>\n }\n }\n </a>\n\n <!-- \u2500\u2500 Sub-navigation (shown when expanded and on this route) \u2500\u2500 -->\n @if (!collapsed() && item.childGroups && isOnRoute(item.route)) {\n <div class=\"mb-1 ml-[1.125rem] border-l border-gray-200/70 pl-3 dark:border-gray-800/50\">\n @for (childGroup of item.childGroups; track childGroup.label) {\n <!-- Sub-group label -->\n <p class=\"mb-0.5 px-1 text-[9.5px] font-semibold uppercase tracking-widest\n text-gray-400/70 dark:text-gray-700\"\n [class.mt-3]=\"!$first\"\n [class.mt-1.5]=\"$first\">\n {{ childGroup.label }}\n </p>\n <!-- Sub-items -->\n @for (child of childGroup.items; track child.id) {\n <a\n [routerLink]=\"[item.route]\"\n [queryParams]=\"{ c: child.id }\"\n [attr.data-nav-item]=\"child.id\"\n [class]=\"childItemClass(child.id)\"\n [attr.aria-current]=\"activeChildId() === child.id ? 'page' : null\"\n (click)=\"mobileClose.emit()\">\n {{ child.label }}\n </a>\n }\n }\n </div>\n }\n }\n }\n </nav>\n\n <!-- ========== Sidebar Footer (pinned) ========== -->\n <div class=\"shrink-0 border-t border-gray-200/80 px-2.5 py-2.5 dark:border-gray-800/60\">\n @if (!collapsed()) {\n <!-- Resources -->\n <div class=\"flex items-center justify-between rounded-lg bg-gray-50/70 px-1.5 py-1.5 dark:bg-gray-800/40\">\n <div class=\"flex items-center gap-0.5\">\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Documentation\" aria-label=\"Documentation\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M11.25 4.533A9.707 9.707 0 006 3a9.735 9.735 0 00-3.25.555.75.75 0 00-.5.707v14.25a.75.75 0 001 .707A8.237 8.237 0 016 18.75c1.995 0 3.823.707 5.25 1.886V4.533zM12.75 20.636A8.214 8.214 0 0118 18.75c.966 0 1.89.166 2.75.47a.75.75 0 001-.708V4.262a.75.75 0 00-.5-.707A9.735 9.735 0 0018 3a9.707 9.707 0 00-5.25 1.533v16.103z\" />\n </svg>\n </a>\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"GitHub\" aria-label=\"GitHub\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n <a [href]=\"npmUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"npm\" aria-label=\"npm package\"\n class=\"inline-flex h-7 items-center justify-center rounded-md px-1.5 font-mono text-[10px] font-bold\n text-gray-400 transition-colors hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n npm\n </a>\n <a [href]=\"communityUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Community\" aria-label=\"Community forum\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4.913 2.658c2.075-.27 4.19-.408 6.337-.408 2.147 0 4.262.139 6.337.408 1.922.25 3.291 1.861 3.405 3.727a4.403 4.403 0 00-1.032-.211 50.89 50.89 0 00-8.42 0c-2.358.196-4.04 2.19-4.04 4.434v4.286a4.47 4.47 0 002.433 3.984L7.28 21.53A.75.75 0 016 21v-4.03a48.527 48.527 0 01-1.087-.128C2.905 16.58 1.5 14.833 1.5 12.862V6.638c0-1.97 1.405-3.718 3.413-3.979z\" />\n <path d=\"M15.75 7.5c-1.376 0-2.739.057-4.086.169C10.124 7.797 9 9.103 9 10.609v4.285c0 1.507 1.128 2.814 2.67 2.94 1.243.102 2.5.157 3.768.165l2.782 2.781a.75.75 0 001.28-.53v-2.39l.33-.026c1.542-.125 2.67-1.433 2.67-2.94v-4.286c0-1.505-1.125-2.811-2.664-2.94A49.392 49.392 0 0015.75 7.5z\" />\n </svg>\n </a>\n </div>\n <span class=\"pr-1 text-[10px] font-semibold text-gray-400 dark:text-gray-600\">v1.0</span>\n </div>\n } @else {\n <!-- Collapsed: GitHub quick link -->\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"GitHub\" aria-label=\"GitHub\"\n class=\"flex items-center justify-center rounded-lg p-2 text-gray-400 transition-colors\n hover:bg-gray-100/80 hover:text-primary-600\n dark:text-gray-500 dark:hover:bg-gray-800/70 dark:hover:text-primary-300\">\n <svg class=\"h-[18px] w-[18px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n }\n </div>\n</aside>" }]
115
+ }], ctorParameters: () => [], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], mobileOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileOpen", required: false }] }], mobileClose: [{ type: i0.Output, args: ["mobileClose"] }], toggleCollapse: [{ type: i0.Output, args: ["toggleCollapse"] }], menuGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuGroups", required: false }] }], docsUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "docsUrl", required: false }] }], githubUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "githubUrl", required: false }] }], npmUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "npmUrl", required: false }] }], communityUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "communityUrl", required: false }] }] } });
116
+
117
+ /**
118
+ * TopbarComponent
119
+ *
120
+ * Slim, premium top navigation bar:
121
+ * - Sidebar collapse (desktop) / hamburger (mobile)
122
+ * - Resource links (Docs, Community, Pricing)
123
+ * - GitHub + npm quick links
124
+ * - Dark / light theme toggle
125
+ * - "Get Pro" call-to-action
126
+ */
127
+ class SvTopbarComponent {
128
+ host = inject(ElementRef);
129
+ /** Whether the small-screen navigation (hamburger) menu is open. */
130
+ menuOpen = signal(false, ...(ngDevMode ? [{ debugName: "menuOpen" }] : /* istanbul ignore next */ []));
131
+ /** Whether the sidebar is currently collapsed (controls toggle icon rotation). */
132
+ sidebarCollapsed = input(false, ...(ngDevMode ? [{ debugName: "sidebarCollapsed" }] : /* istanbul ignore next */ []));
133
+ /** Overview / landing page. Internal routes use SPA navigation. */
134
+ overviewUrl = input('/get-started', ...(ngDevMode ? [{ debugName: "overviewUrl" }] : /* istanbul ignore next */ []));
135
+ /** Documentation page. Internal routes ('/docs') use SPA navigation; an
136
+ * external URL ('https://…') opens in a new tab. */
137
+ docsUrl = input('/docs', ...(ngDevMode ? [{ debugName: "docsUrl" }] : /* istanbul ignore next */ []));
138
+ /** Community / forum URL. */
139
+ communityUrl = input('#community', ...(ngDevMode ? [{ debugName: "communityUrl" }] : /* istanbul ignore next */ []));
140
+ /** Pricing page — internal route by default; also used by the "Get Pro" CTA. */
141
+ pricingUrl = input('/pricing', ...(ngDevMode ? [{ debugName: "pricingUrl" }] : /* istanbul ignore next */ []));
142
+ /** Shared styling for the inline text resource links. */
143
+ resourceLinkClass = 'rounded-lg px-2.5 py-1.5 text-[13px] font-medium text-gray-600 transition-colors ' +
144
+ 'hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white';
145
+ /** Shared styling for the small-screen overflow-menu items. */
146
+ menuItemClass = 'block rounded-lg px-2.5 py-2 text-[13px] font-medium text-gray-600 transition-colors ' +
147
+ 'hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white';
148
+ /** True for in-app routes (start with '/') — rendered with routerLink. */
149
+ isInternal(url) {
150
+ return url.startsWith('/');
151
+ }
152
+ toggleMenu() {
153
+ this.menuOpen.update((open) => !open);
154
+ }
155
+ onDocumentPointerDown(event) {
156
+ if (this.menuOpen() && !this.host.nativeElement.contains(event.target)) {
157
+ this.menuOpen.set(false);
158
+ }
159
+ }
160
+ /**
161
+ * Whether to render the built-in "Get Pro" CTA. Set false when the host
162
+ * projects its own Pro control into the [topbar-actions] slot (avoids a
163
+ * duplicate button). Defaults true for standalone library use.
164
+ */
165
+ showGetPro = input(true, ...(ngDevMode ? [{ debugName: "showGetPro" }] : /* istanbul ignore next */ []));
166
+ /** GitHub repository URL. */
167
+ githubUrl = input('#github', ...(ngDevMode ? [{ debugName: "githubUrl" }] : /* istanbul ignore next */ []));
168
+ /** npm package URL. */
169
+ npmUrl = input('#npm', ...(ngDevMode ? [{ debugName: "npmUrl" }] : /* istanbul ignore next */ []));
170
+ // ── Outputs ────────────────────────────────────────────────────────────────
171
+ /** Emits when the desktop sidebar toggle is clicked. */
172
+ toggleSidebar = output();
173
+ /** Emits when the mobile hamburger menu is clicked. */
174
+ toggleMobileSidebar = output();
175
+ // ── Services ───────────────────────────────────────────────────────────────
176
+ /** @internal Theme service — used by the template's dark-mode toggle. */
177
+ themeService = inject(ThemeService);
178
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
179
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvTopbarComponent, isStandalone: true, selector: "sv-topbar", inputs: { sidebarCollapsed: { classPropertyName: "sidebarCollapsed", publicName: "sidebarCollapsed", isSignal: true, isRequired: false, transformFunction: null }, overviewUrl: { classPropertyName: "overviewUrl", publicName: "overviewUrl", isSignal: true, isRequired: false, transformFunction: null }, docsUrl: { classPropertyName: "docsUrl", publicName: "docsUrl", isSignal: true, isRequired: false, transformFunction: null }, communityUrl: { classPropertyName: "communityUrl", publicName: "communityUrl", isSignal: true, isRequired: false, transformFunction: null }, pricingUrl: { classPropertyName: "pricingUrl", publicName: "pricingUrl", isSignal: true, isRequired: false, transformFunction: null }, showGetPro: { classPropertyName: "showGetPro", publicName: "showGetPro", isSignal: true, isRequired: false, transformFunction: null }, githubUrl: { classPropertyName: "githubUrl", publicName: "githubUrl", isSignal: true, isRequired: false, transformFunction: null }, npmUrl: { classPropertyName: "npmUrl", publicName: "npmUrl", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggleSidebar: "toggleSidebar", toggleMobileSidebar: "toggleMobileSidebar" }, host: { listeners: { "document:keydown.escape": "menuOpen.set(false)", "document:pointerdown": "onDocumentPointerDown($event)" } }, ngImport: i0, template: "<!-- ============================================================\n Topbar Navigation\n ============================================================ -->\n\n<header\n class=\"sticky top-0 z-30 flex h-16 items-center gap-2 border-b border-white/70 bg-white/72\n px-4 shadow-[0_10px_26px_-22px_rgba(15,23,42,0.65)] backdrop-blur-xl dark:border-gray-700/45\n dark:bg-gray-900/62 dark:shadow-[0_14px_34px_-24px_rgba(2,6,23,0.95)] sm:px-5\">\n\n <!-- ========== Left: Menu Toggles ========== -->\n <div class=\"flex shrink-0 items-center gap-1.5\">\n <!-- Mobile hamburger (opens the sidebar) -->\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200 lg:hidden\"\n (click)=\"toggleMobileSidebar.emit()\"\n aria-label=\"Open sidebar\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n\n <!-- Desktop sidebar toggle -->\n <button\n type=\"button\"\n class=\"group hidden items-center justify-center rounded-lg border border-gray-200/80 bg-white/60 p-2\n text-gray-500 shadow-sm transition-all hover:border-gray-300 hover:bg-white hover:text-gray-800\n active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500\n focus-visible:ring-offset-1 dark:border-gray-700/70 dark:bg-gray-800/50 dark:text-gray-400\n dark:hover:border-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-100\n dark:focus-visible:ring-offset-gray-900 lg:inline-flex\"\n (click)=\"toggleSidebar.emit()\"\n [attr.aria-label]=\"sidebarCollapsed() ? 'Expand sidebar' : 'Collapse sidebar'\"\n [attr.aria-pressed]=\"!sidebarCollapsed()\"\n [attr.title]=\"sidebarCollapsed() ? 'Expand sidebar' : 'Collapse sidebar'\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.6\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"4.5\" width=\"18\" height=\"15\" rx=\"2.5\" />\n <path stroke-linecap=\"round\" d=\"M9 4.5v15\" />\n <path class=\"text-primary-500\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.8\"\n [attr.d]=\"sidebarCollapsed() ? 'M13.5 9.5l2.5 2.5-2.5 2.5' : 'M16 9.5L13.5 12l2.5 2.5'\" />\n </svg>\n </button>\n </div>\n\n <!-- ========== Center: Search (host-projected) ========== -->\n <div class=\"min-w-0 flex-1\">\n <div class=\"max-w-md\">\n <ng-content select=\"[topbar-search]\"></ng-content>\n </div>\n </div>\n\n <!-- ========== Right: Nav + Actions ========== -->\n <div class=\"flex shrink-0 items-center gap-1 sm:gap-1.5\">\n\n <!-- Primary nav (inline on large screens) -->\n <nav class=\"hidden items-center lg:flex\" aria-label=\"Primary\">\n @if (isInternal(overviewUrl())) {\n <a [routerLink]=\"overviewUrl()\" [class]=\"resourceLinkClass\">Overview</a>\n } @else {\n <a [href]=\"overviewUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Overview</a>\n }\n @if (isInternal(docsUrl())) {\n <a [routerLink]=\"docsUrl()\" [class]=\"resourceLinkClass\">Docs</a>\n } @else {\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Docs</a>\n }\n @if (isInternal(pricingUrl())) {\n <a [routerLink]=\"pricingUrl()\" [class]=\"resourceLinkClass\">Pricing</a>\n } @else {\n <a [href]=\"pricingUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Pricing</a>\n }\n </nav>\n\n <span class=\"mx-1 hidden h-5 w-px bg-gray-200/90 dark:bg-gray-700/70 lg:block\"></span>\n\n <!-- GitHub (large screens) -->\n <a\n [href]=\"githubUrl()\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"hidden rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white lg:inline-flex\"\n aria-label=\"View on GitHub\">\n <svg class=\"h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n\n <!-- npm (large screens) -->\n <a\n [href]=\"npmUrl()\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"hidden items-center rounded-md border border-gray-200/90 px-1.5 py-1 font-mono text-[11px]\n font-semibold text-gray-500 transition-colors hover:border-primary-300 hover:text-primary-600\n dark:border-gray-700/80 dark:text-gray-400 dark:hover:border-primary-700 dark:hover:text-primary-300\n lg:inline-flex\"\n aria-label=\"View on npm\">\n npm\n </a>\n\n <!-- Theme Toggle (always visible) -->\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200\"\n (click)=\"themeService.toggleTheme()\"\n [attr.aria-label]=\"themeService.isDark() ? 'Switch to light mode' : 'Switch to dark mode'\">\n @if (themeService.isDark()) {\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z\" />\n </svg>\n } @else {\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z\" />\n </svg>\n }\n </button>\n\n <!-- Host-projected actions (e.g. Pro license status control) -->\n <ng-content select=\"[topbar-actions]\"></ng-content>\n\n <!-- Get Pro CTA (large screens; hidden when the host projects its own control) -->\n @if (showGetPro()) {\n <a\n [href]=\"pricingUrl()\"\n [attr.target]=\"isInternal(pricingUrl()) ? null : '_blank'\"\n rel=\"noopener noreferrer\"\n class=\"ml-0.5 hidden items-center gap-1.5 rounded-lg bg-gradient-to-r from-primary-600 to-cyan-500\n px-3.5 py-2 text-[13px] font-semibold text-white shadow-sm transition-all\n hover:from-primary-700 hover:to-cyan-600 hover:shadow-md\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:focus-visible:ring-offset-gray-900 lg:inline-flex\">\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.562.562 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.562.562 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z\" />\n </svg>\n <span class=\"hidden sm:inline\">Get Pro</span>\n </a>\n }\n\n <!-- ========== Overflow menu (small screens) ========== -->\n <div class=\"relative lg:hidden\">\n <button\n type=\"button\"\n (click)=\"toggleMenu()\"\n [attr.aria-expanded]=\"menuOpen()\"\n aria-label=\"More navigation\"\n class=\"flex items-center justify-center rounded-lg border border-gray-200/80 bg-white/60 p-2 text-gray-500\n shadow-sm transition-all hover:border-gray-300 hover:bg-white hover:text-gray-800 active:scale-95\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:border-gray-700/70 dark:bg-gray-800/50 dark:text-gray-400 dark:hover:border-gray-600\n dark:hover:bg-gray-800 dark:hover:text-gray-100 dark:focus-visible:ring-offset-gray-900\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.7\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n\n @if (menuOpen()) {\n <div class=\"absolute right-0 top-[calc(100%+0.6rem)] z-40 w-56 overflow-hidden rounded-xl border\n border-gray-200/70 bg-white/95 p-1.5 shadow-[0_20px_50px_-20px_rgba(15,23,42,0.5)]\n ring-1 ring-black/[0.03] backdrop-blur-xl dark:border-gray-700/50 dark:bg-gray-900/95\n dark:ring-white/[0.04]\"\n role=\"menu\">\n <p class=\"px-2.5 pb-1 pt-1.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-600\">\n Navigation\n </p>\n @if (isInternal(overviewUrl())) {\n <a [routerLink]=\"overviewUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Overview</a>\n } @else {\n <a [href]=\"overviewUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Overview</a>\n }\n @if (isInternal(docsUrl())) {\n <a [routerLink]=\"docsUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Docs</a>\n } @else {\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Docs</a>\n }\n @if (isInternal(pricingUrl())) {\n <a [routerLink]=\"pricingUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Pricing</a>\n } @else {\n <a [href]=\"pricingUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Pricing</a>\n }\n\n <div class=\"my-1 border-t border-gray-100 dark:border-gray-800\"></div>\n\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">GitHub</a>\n <a [href]=\"npmUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">npm</a>\n <a [href]=\"communityUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Community</a>\n </div>\n }\n </div>\n </div>\n</header>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
180
+ }
181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvTopbarComponent, decorators: [{
182
+ type: Component,
183
+ args: [{ selector: 'sv-topbar', standalone: true, imports: [RouterLink], changeDetection: ChangeDetectionStrategy.OnPush, host: {
184
+ '(document:keydown.escape)': 'menuOpen.set(false)',
185
+ '(document:pointerdown)': 'onDocumentPointerDown($event)',
186
+ }, template: "<!-- ============================================================\n Topbar Navigation\n ============================================================ -->\n\n<header\n class=\"sticky top-0 z-30 flex h-16 items-center gap-2 border-b border-white/70 bg-white/72\n px-4 shadow-[0_10px_26px_-22px_rgba(15,23,42,0.65)] backdrop-blur-xl dark:border-gray-700/45\n dark:bg-gray-900/62 dark:shadow-[0_14px_34px_-24px_rgba(2,6,23,0.95)] sm:px-5\">\n\n <!-- ========== Left: Menu Toggles ========== -->\n <div class=\"flex shrink-0 items-center gap-1.5\">\n <!-- Mobile hamburger (opens the sidebar) -->\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200 lg:hidden\"\n (click)=\"toggleMobileSidebar.emit()\"\n aria-label=\"Open sidebar\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n\n <!-- Desktop sidebar toggle -->\n <button\n type=\"button\"\n class=\"group hidden items-center justify-center rounded-lg border border-gray-200/80 bg-white/60 p-2\n text-gray-500 shadow-sm transition-all hover:border-gray-300 hover:bg-white hover:text-gray-800\n active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500\n focus-visible:ring-offset-1 dark:border-gray-700/70 dark:bg-gray-800/50 dark:text-gray-400\n dark:hover:border-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-100\n dark:focus-visible:ring-offset-gray-900 lg:inline-flex\"\n (click)=\"toggleSidebar.emit()\"\n [attr.aria-label]=\"sidebarCollapsed() ? 'Expand sidebar' : 'Collapse sidebar'\"\n [attr.aria-pressed]=\"!sidebarCollapsed()\"\n [attr.title]=\"sidebarCollapsed() ? 'Expand sidebar' : 'Collapse sidebar'\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.6\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"4.5\" width=\"18\" height=\"15\" rx=\"2.5\" />\n <path stroke-linecap=\"round\" d=\"M9 4.5v15\" />\n <path class=\"text-primary-500\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.8\"\n [attr.d]=\"sidebarCollapsed() ? 'M13.5 9.5l2.5 2.5-2.5 2.5' : 'M16 9.5L13.5 12l2.5 2.5'\" />\n </svg>\n </button>\n </div>\n\n <!-- ========== Center: Search (host-projected) ========== -->\n <div class=\"min-w-0 flex-1\">\n <div class=\"max-w-md\">\n <ng-content select=\"[topbar-search]\"></ng-content>\n </div>\n </div>\n\n <!-- ========== Right: Nav + Actions ========== -->\n <div class=\"flex shrink-0 items-center gap-1 sm:gap-1.5\">\n\n <!-- Primary nav (inline on large screens) -->\n <nav class=\"hidden items-center lg:flex\" aria-label=\"Primary\">\n @if (isInternal(overviewUrl())) {\n <a [routerLink]=\"overviewUrl()\" [class]=\"resourceLinkClass\">Overview</a>\n } @else {\n <a [href]=\"overviewUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Overview</a>\n }\n @if (isInternal(docsUrl())) {\n <a [routerLink]=\"docsUrl()\" [class]=\"resourceLinkClass\">Docs</a>\n } @else {\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Docs</a>\n }\n @if (isInternal(pricingUrl())) {\n <a [routerLink]=\"pricingUrl()\" [class]=\"resourceLinkClass\">Pricing</a>\n } @else {\n <a [href]=\"pricingUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Pricing</a>\n }\n </nav>\n\n <span class=\"mx-1 hidden h-5 w-px bg-gray-200/90 dark:bg-gray-700/70 lg:block\"></span>\n\n <!-- GitHub (large screens) -->\n <a\n [href]=\"githubUrl()\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"hidden rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white lg:inline-flex\"\n aria-label=\"View on GitHub\">\n <svg class=\"h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n\n <!-- npm (large screens) -->\n <a\n [href]=\"npmUrl()\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"hidden items-center rounded-md border border-gray-200/90 px-1.5 py-1 font-mono text-[11px]\n font-semibold text-gray-500 transition-colors hover:border-primary-300 hover:text-primary-600\n dark:border-gray-700/80 dark:text-gray-400 dark:hover:border-primary-700 dark:hover:text-primary-300\n lg:inline-flex\"\n aria-label=\"View on npm\">\n npm\n </a>\n\n <!-- Theme Toggle (always visible) -->\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200\"\n (click)=\"themeService.toggleTheme()\"\n [attr.aria-label]=\"themeService.isDark() ? 'Switch to light mode' : 'Switch to dark mode'\">\n @if (themeService.isDark()) {\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z\" />\n </svg>\n } @else {\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z\" />\n </svg>\n }\n </button>\n\n <!-- Host-projected actions (e.g. Pro license status control) -->\n <ng-content select=\"[topbar-actions]\"></ng-content>\n\n <!-- Get Pro CTA (large screens; hidden when the host projects its own control) -->\n @if (showGetPro()) {\n <a\n [href]=\"pricingUrl()\"\n [attr.target]=\"isInternal(pricingUrl()) ? null : '_blank'\"\n rel=\"noopener noreferrer\"\n class=\"ml-0.5 hidden items-center gap-1.5 rounded-lg bg-gradient-to-r from-primary-600 to-cyan-500\n px-3.5 py-2 text-[13px] font-semibold text-white shadow-sm transition-all\n hover:from-primary-700 hover:to-cyan-600 hover:shadow-md\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:focus-visible:ring-offset-gray-900 lg:inline-flex\">\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.562.562 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.562.562 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z\" />\n </svg>\n <span class=\"hidden sm:inline\">Get Pro</span>\n </a>\n }\n\n <!-- ========== Overflow menu (small screens) ========== -->\n <div class=\"relative lg:hidden\">\n <button\n type=\"button\"\n (click)=\"toggleMenu()\"\n [attr.aria-expanded]=\"menuOpen()\"\n aria-label=\"More navigation\"\n class=\"flex items-center justify-center rounded-lg border border-gray-200/80 bg-white/60 p-2 text-gray-500\n shadow-sm transition-all hover:border-gray-300 hover:bg-white hover:text-gray-800 active:scale-95\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:border-gray-700/70 dark:bg-gray-800/50 dark:text-gray-400 dark:hover:border-gray-600\n dark:hover:bg-gray-800 dark:hover:text-gray-100 dark:focus-visible:ring-offset-gray-900\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.7\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n\n @if (menuOpen()) {\n <div class=\"absolute right-0 top-[calc(100%+0.6rem)] z-40 w-56 overflow-hidden rounded-xl border\n border-gray-200/70 bg-white/95 p-1.5 shadow-[0_20px_50px_-20px_rgba(15,23,42,0.5)]\n ring-1 ring-black/[0.03] backdrop-blur-xl dark:border-gray-700/50 dark:bg-gray-900/95\n dark:ring-white/[0.04]\"\n role=\"menu\">\n <p class=\"px-2.5 pb-1 pt-1.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-600\">\n Navigation\n </p>\n @if (isInternal(overviewUrl())) {\n <a [routerLink]=\"overviewUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Overview</a>\n } @else {\n <a [href]=\"overviewUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Overview</a>\n }\n @if (isInternal(docsUrl())) {\n <a [routerLink]=\"docsUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Docs</a>\n } @else {\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Docs</a>\n }\n @if (isInternal(pricingUrl())) {\n <a [routerLink]=\"pricingUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Pricing</a>\n } @else {\n <a [href]=\"pricingUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Pricing</a>\n }\n\n <div class=\"my-1 border-t border-gray-100 dark:border-gray-800\"></div>\n\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">GitHub</a>\n <a [href]=\"npmUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">npm</a>\n <a [href]=\"communityUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Community</a>\n </div>\n }\n </div>\n </div>\n</header>\n" }]
187
+ }], propDecorators: { sidebarCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarCollapsed", required: false }] }], overviewUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "overviewUrl", required: false }] }], docsUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "docsUrl", required: false }] }], communityUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "communityUrl", required: false }] }], pricingUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "pricingUrl", required: false }] }], showGetPro: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGetPro", required: false }] }], githubUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "githubUrl", required: false }] }], npmUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "npmUrl", required: false }] }], toggleSidebar: [{ type: i0.Output, args: ["toggleSidebar"] }], toggleMobileSidebar: [{ type: i0.Output, args: ["toggleMobileSidebar"] }] } });
188
+
189
+ /**
190
+ * FooterComponent
191
+ *
192
+ * Simple dashboard footer with copyright and version info.
193
+ * Sticks to the bottom of the content area (not the viewport).
194
+ */
195
+ class SvFooterComponent {
196
+ currentYear = new Date().getFullYear();
197
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
198
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.13", type: SvFooterComponent, isStandalone: true, selector: "sv-footer", ngImport: i0, template: "<!-- ============================================================\n Footer\n ============================================================ -->\n\n<footer class=\"border-t border-white/65 bg-white/65 px-5 py-3 backdrop-blur-md dark:border-gray-700/40 dark:bg-gray-900/55\">\n <div class=\"mx-auto flex w-full max-w-[1440px] flex-col items-center justify-between gap-1 text-[11px] text-gray-500/80 dark:text-gray-500 sm:flex-row\">\n <p>&copy; {{ currentYear }} styloviz</p>\n <p>Angular 21 &amp; Tailwind CSS 4</p>\n </div>\n</footer>", changeDetection: i0.ChangeDetectionStrategy.OnPush });
199
+ }
200
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvFooterComponent, decorators: [{
201
+ type: Component,
202
+ args: [{ selector: 'sv-footer', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- ============================================================\n Footer\n ============================================================ -->\n\n<footer class=\"border-t border-white/65 bg-white/65 px-5 py-3 backdrop-blur-md dark:border-gray-700/40 dark:bg-gray-900/55\">\n <div class=\"mx-auto flex w-full max-w-[1440px] flex-col items-center justify-between gap-1 text-[11px] text-gray-500/80 dark:text-gray-500 sm:flex-row\">\n <p>&copy; {{ currentYear }} styloviz</p>\n <p>Angular 21 &amp; Tailwind CSS 4</p>\n </div>\n</footer>" }]
203
+ }] });
204
+
205
+ /**
206
+ * PageHeaderComponent
207
+ *
208
+ * Reusable page title section with:
209
+ * - Title and subtitle
210
+ * - Optional description
211
+ * - Content projection slot for action buttons
212
+ *
213
+ * Usage:
214
+ * <sv-page-header
215
+ * title="Analytics Dashboard"
216
+ * subtitle="Monitor your key metrics and performance.">
217
+ * <button class="...">Export</button> <!-- projected into actions slot -->
218
+ * </sv-page-header>
219
+ */
220
+ class SvPageHeaderComponent {
221
+ /** Page title */
222
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
223
+ /** Optional subtitle / description */
224
+ subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
225
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
226
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvPageHeaderComponent, isStandalone: true, selector: "sv-page-header", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- ============================================================\n Page Header\n ============================================================ -->\n\n<div class=\"mb-7 flex flex-col gap-3 rounded-2xl border border-white/70 bg-white/75 px-5 py-4\n shadow-[0_14px_32px_-24px_rgba(15,23,42,0.55)] backdrop-blur-sm dark:border-gray-700/40\n dark:bg-gray-900/62 dark:shadow-[0_18px_38px_-28px_rgba(2,6,23,0.95)] sm:flex-row sm:items-center sm:justify-between\">\n <!-- Title area -->\n <div>\n <h1 class=\"text-xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-[1.35rem]\">\n {{ title() }}\n </h1>\n @if (subtitle()) {\n <p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">\n {{ subtitle() }}\n </p>\n }\n </div>\n\n <!-- Actions slot (projected content) -->\n <div class=\"flex items-center gap-2\">\n <ng-content />\n </div>\n</div>", changeDetection: i0.ChangeDetectionStrategy.OnPush });
227
+ }
228
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvPageHeaderComponent, decorators: [{
229
+ type: Component,
230
+ args: [{ selector: 'sv-page-header', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- ============================================================\n Page Header\n ============================================================ -->\n\n<div class=\"mb-7 flex flex-col gap-3 rounded-2xl border border-white/70 bg-white/75 px-5 py-4\n shadow-[0_14px_32px_-24px_rgba(15,23,42,0.55)] backdrop-blur-sm dark:border-gray-700/40\n dark:bg-gray-900/62 dark:shadow-[0_18px_38px_-28px_rgba(2,6,23,0.95)] sm:flex-row sm:items-center sm:justify-between\">\n <!-- Title area -->\n <div>\n <h1 class=\"text-xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-[1.35rem]\">\n {{ title() }}\n </h1>\n @if (subtitle()) {\n <p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">\n {{ subtitle() }}\n </p>\n }\n </div>\n\n <!-- Actions slot (projected content) -->\n <div class=\"flex items-center gap-2\">\n <ng-content />\n </div>\n</div>" }]
231
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }] } });
232
+
233
+ /**
234
+ * DashboardShellComponent
235
+ *
236
+ * The master layout wrapper. Every dashboard page renders inside this shell.
237
+ * It composes the sidebar, topbar, main content area, and footer.
238
+ *
239
+ * Layout structure:
240
+ * ┌──────────┬────────────────────────────────┐
241
+ * │ │ Topbar │
242
+ * │ Sidebar ├────────────────────────────────┤
243
+ * │ │ <router-outlet> (page content) │
244
+ * │ ├────────────────────────────────┤
245
+ * │ │ Footer │
246
+ * └──────────┴────────────────────────────────┘
247
+ */
248
+ class SvDashboardShellComponent {
249
+ /** Demo user displayed in the topbar. In a real app this comes from an auth service. */
250
+ currentUser = { name: 'Alex Johnson', email: 'alex@example.com', avatar: 'https://i.pravatar.cc/150?img=12' };
251
+ /**
252
+ * Controls whether the sidebar is collapsed (icons only) or expanded.
253
+ * Signal makes this reactive — topbar toggle button and sidebar both stay in sync.
254
+ */
255
+ sidebarCollapsed = signal(false, ...(ngDevMode ? [{ debugName: "sidebarCollapsed" }] : /* istanbul ignore next */ []));
256
+ /**
257
+ * Controls mobile sidebar visibility.
258
+ * On mobile, the sidebar is hidden by default and slides in as an overlay.
259
+ */
260
+ mobileSidebarOpen = signal(false, ...(ngDevMode ? [{ debugName: "mobileSidebarOpen" }] : /* istanbul ignore next */ []));
261
+ /**
262
+ * Toggle sidebar between collapsed and expanded states (desktop).
263
+ */
264
+ toggleSidebar() {
265
+ this.sidebarCollapsed.update((collapsed) => !collapsed);
266
+ }
267
+ /**
268
+ * Toggle mobile sidebar overlay.
269
+ */
270
+ toggleMobileSidebar() {
271
+ this.mobileSidebarOpen.update((open) => !open);
272
+ }
273
+ /**
274
+ * Close mobile sidebar (e.g., after navigation or backdrop click).
275
+ */
276
+ closeMobileSidebar() {
277
+ this.mobileSidebarOpen.set(false);
278
+ }
279
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvDashboardShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
280
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvDashboardShellComponent, isStandalone: true, selector: "sv-dashboard-shell", ngImport: i0, template: "<!-- ============================================================\n Dashboard Shell Layout\n ============================================================\n Desktop: sidebar on left, content on right\n Mobile: sidebar hidden, hamburger menu in topbar\n ============================================================ -->\n\n<div class=\"relative flex h-screen overflow-hidden bg-[linear-gradient(180deg,#f8fafc_0%,#eef2ff_48%,#ecfeff_100%)] dark:bg-[linear-gradient(180deg,#020617_0%,#0f172a_52%,#111827_100%)]\">\n\n <div class=\"pointer-events-none absolute inset-0\">\n <div class=\"absolute -right-24 -top-24 h-72 w-72 rounded-full bg-primary-300/25 blur-3xl dark:bg-primary-600/20\"></div>\n <div class=\"absolute bottom-[-8rem] left-[-6rem] h-80 w-80 rounded-full bg-emerald-300/20 blur-3xl dark:bg-emerald-500/15\"></div>\n </div>\n\n <!-- ========== Mobile Sidebar Backdrop ========== -->\n @if (mobileSidebarOpen()) {\n <div\n class=\"fixed inset-0 z-40 bg-black/50 backdrop-blur-sm lg:hidden\"\n (click)=\"closeMobileSidebar()\"\n aria-hidden=\"true\">\n </div>\n }\n\n <!-- ========== Sidebar ========== -->\n <sv-sidebar\n [collapsed]=\"sidebarCollapsed()\"\n [mobileOpen]=\"mobileSidebarOpen()\"\n (mobileClose)=\"closeMobileSidebar()\"\n (toggleCollapse)=\"toggleSidebar()\"\n />\n\n <!-- ========== Main Content Area ========== -->\n <div\n class=\"relative z-10 flex min-w-0 flex-1 flex-col overflow-hidden transition-all duration-300\"\n [class.lg:ml-64]=\"!sidebarCollapsed()\"\n [class.lg:ml-20]=\"sidebarCollapsed()\">\n\n <!-- Topbar -->\n <sv-topbar\n [sidebarCollapsed]=\"sidebarCollapsed()\"\n (toggleSidebar)=\"toggleSidebar()\"\n (toggleMobileSidebar)=\"toggleMobileSidebar()\"\n />\n\n <!-- Page Content -->\n <main class=\"flex-1 overflow-y-auto px-4 pb-8 pt-5 sm:px-6 sm:pt-6 lg:px-8 lg:pt-7\">\n <div class=\"mx-auto w-full max-w-[1440px]\">\n <router-outlet />\n </div>\n </main>\n\n <!-- Footer -->\n <sv-footer />\n </div>\n\n <!-- Global toast stacks \u2014 one host per position, each filters its own toasts -->\n <sv-toast-host position=\"top-left\" />\n <sv-toast-host position=\"top-center\" />\n <sv-toast-host position=\"top-right\" />\n <sv-toast-host position=\"bottom-left\" />\n <sv-toast-host position=\"bottom-center\" />\n <sv-toast-host position=\"bottom-right\" />\n</div>", dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: SvSidebarComponent, selector: "sv-sidebar", inputs: ["collapsed", "mobileOpen", "menuGroups", "docsUrl", "githubUrl", "npmUrl", "communityUrl"], outputs: ["mobileClose", "toggleCollapse"] }, { kind: "component", type: SvTopbarComponent, selector: "sv-topbar", inputs: ["sidebarCollapsed", "overviewUrl", "docsUrl", "communityUrl", "pricingUrl", "showGetPro", "githubUrl", "npmUrl"], outputs: ["toggleSidebar", "toggleMobileSidebar"] }, { kind: "component", type: SvFooterComponent, selector: "sv-footer" }, { kind: "component", type: SvToastHostComponent, selector: "sv-toast-host", inputs: ["position", "maxVisible"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
281
+ }
282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvDashboardShellComponent, decorators: [{
283
+ type: Component,
284
+ args: [{ selector: 'sv-dashboard-shell', standalone: true, imports: [RouterOutlet, SvSidebarComponent, SvTopbarComponent, SvFooterComponent, SvToastHostComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- ============================================================\n Dashboard Shell Layout\n ============================================================\n Desktop: sidebar on left, content on right\n Mobile: sidebar hidden, hamburger menu in topbar\n ============================================================ -->\n\n<div class=\"relative flex h-screen overflow-hidden bg-[linear-gradient(180deg,#f8fafc_0%,#eef2ff_48%,#ecfeff_100%)] dark:bg-[linear-gradient(180deg,#020617_0%,#0f172a_52%,#111827_100%)]\">\n\n <div class=\"pointer-events-none absolute inset-0\">\n <div class=\"absolute -right-24 -top-24 h-72 w-72 rounded-full bg-primary-300/25 blur-3xl dark:bg-primary-600/20\"></div>\n <div class=\"absolute bottom-[-8rem] left-[-6rem] h-80 w-80 rounded-full bg-emerald-300/20 blur-3xl dark:bg-emerald-500/15\"></div>\n </div>\n\n <!-- ========== Mobile Sidebar Backdrop ========== -->\n @if (mobileSidebarOpen()) {\n <div\n class=\"fixed inset-0 z-40 bg-black/50 backdrop-blur-sm lg:hidden\"\n (click)=\"closeMobileSidebar()\"\n aria-hidden=\"true\">\n </div>\n }\n\n <!-- ========== Sidebar ========== -->\n <sv-sidebar\n [collapsed]=\"sidebarCollapsed()\"\n [mobileOpen]=\"mobileSidebarOpen()\"\n (mobileClose)=\"closeMobileSidebar()\"\n (toggleCollapse)=\"toggleSidebar()\"\n />\n\n <!-- ========== Main Content Area ========== -->\n <div\n class=\"relative z-10 flex min-w-0 flex-1 flex-col overflow-hidden transition-all duration-300\"\n [class.lg:ml-64]=\"!sidebarCollapsed()\"\n [class.lg:ml-20]=\"sidebarCollapsed()\">\n\n <!-- Topbar -->\n <sv-topbar\n [sidebarCollapsed]=\"sidebarCollapsed()\"\n (toggleSidebar)=\"toggleSidebar()\"\n (toggleMobileSidebar)=\"toggleMobileSidebar()\"\n />\n\n <!-- Page Content -->\n <main class=\"flex-1 overflow-y-auto px-4 pb-8 pt-5 sm:px-6 sm:pt-6 lg:px-8 lg:pt-7\">\n <div class=\"mx-auto w-full max-w-[1440px]\">\n <router-outlet />\n </div>\n </main>\n\n <!-- Footer -->\n <sv-footer />\n </div>\n\n <!-- Global toast stacks \u2014 one host per position, each filters its own toasts -->\n <sv-toast-host position=\"top-left\" />\n <sv-toast-host position=\"top-center\" />\n <sv-toast-host position=\"top-right\" />\n <sv-toast-host position=\"bottom-left\" />\n <sv-toast-host position=\"bottom-center\" />\n <sv-toast-host position=\"bottom-right\" />\n</div>" }]
285
+ }] });
286
+
287
+ // ─── Sidebar ──────────────────────────────────────────────────────────────────
288
+
289
+ /**
290
+ * Generated bundle index. Do not edit.
291
+ */
292
+
293
+ export { SvDashboardShellComponent, SvFooterComponent, SvPageHeaderComponent, SvSidebarComponent, SvTopbarComponent };
294
+ //# sourceMappingURL=styloviz-layout.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styloviz-layout.mjs","sources":["../../../../projects/layout/src/sidebar/sidebar.component.ts","../../../../projects/layout/src/sidebar/sidebar.component.html","../../../../projects/layout/src/topbar/topbar.component.ts","../../../../projects/layout/src/topbar/topbar.component.html","../../../../projects/layout/src/footer/footer.component.ts","../../../../projects/layout/src/footer/footer.component.html","../../../../projects/layout/src/page-header/page-header.component.ts","../../../../projects/layout/src/page-header/page-header.component.html","../../../../projects/layout/src/dashboard-shell/dashboard-shell.component.ts","../../../../projects/layout/src/dashboard-shell/dashboard-shell.component.html","../../../../projects/layout/src/public-api.ts","../../../../projects/layout/src/styloviz-layout.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, ElementRef, computed, effect, inject, input, output } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router';\nimport { filter, map, startWith } from 'rxjs';\nimport { MenuGroup } from '@styloviz/core';\n\n/**\n * SidebarComponent\n *\n * Collapsible navigation sidebar with grouped menu items.\n *\n * Features:\n * - Expanded mode: shows icons + labels + group headings\n * - Collapsed mode: shows icons only with tooltip-style labels\n * - Mobile mode: slides in as overlay from the left\n * - Active route highlighting via RouterLinkActive\n * - Accessible keyboard navigation\n */\n@Component({\n selector: 'sv-sidebar',\n standalone: true,\n imports: [RouterLink, RouterLinkActive],\n templateUrl: './sidebar.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SvSidebarComponent {\n /** Whether the sidebar is in collapsed (icon-only) mode */\n collapsed = input<boolean>(false);\n\n /** Whether the mobile sidebar overlay is open */\n mobileOpen = input<boolean>(false);\n\n /** Emits when the mobile sidebar should close */\n mobileClose = output<void>();\n\n /** Emits when the user clicks the collapse/expand toggle inside the sidebar */\n toggleCollapse = output<void>();\n\n /**\n * Navigation menu groups injected by the parent shell.\n * Defaults to the built-in NAVIGATION_ITEMS so the component\n * works out-of-the-box, but buyers can pass their own structure.\n */\n menuGroups = input<MenuGroup[]>([]);\n\n // ── Footer resource links (override to point at your own URLs) ───────────\n\n /** Documentation site URL. */\n readonly docsUrl = input<string>('#docs');\n\n /** GitHub repository URL. */\n readonly githubUrl = input<string>('#github');\n\n /** npm package URL. */\n readonly npmUrl = input<string>('#npm');\n\n /** Community / forum URL. */\n readonly communityUrl = input<string>('#community');\n\n // ── Router-aware sub-navigation ─────────────────────────────────────────\n\n private readonly router = inject(Router);\n\n /** Host element — used to locate and center the active nav item. */\n private readonly host: ElementRef<HTMLElement> = inject(ElementRef);\n\n constructor() {\n // Keep the selected navigation item centered in the sidebar viewport\n // whenever the selection changes (and on initial load).\n effect(() => {\n const id = this.activeChildId();\n if (!id || typeof requestAnimationFrame === 'undefined') return;\n // Defer to the next frame so the active item (and its expanded\n // sub-navigation) are present in the DOM before we measure/scroll.\n requestAnimationFrame(() => {\n const el = this.host.nativeElement.querySelector<HTMLElement>(\n `[data-nav-item=\"${CSS.escape(id)}\"]`,\n );\n if (!el) return;\n const reduceMotion =\n typeof matchMedia !== 'undefined' &&\n matchMedia('(prefers-reduced-motion: reduce)').matches;\n el.scrollIntoView({ block: 'center', behavior: reduceMotion ? 'auto' : 'smooth' });\n });\n });\n }\n\n /** Reactive snapshot of the current URL, updated on every navigation. */\n private readonly routerUrl = toSignal(\n this.router.events.pipe(\n filter(e => e instanceof NavigationEnd),\n map(() => this.router.url),\n startWith(this.router.url),\n ),\n { initialValue: this.router.url },\n );\n\n /**\n * ID of the active child item (read from ?c= query param), or `null` on a\n * bare `/components` route — which shows the dashboard overview, so no child\n * item should appear selected.\n */\n readonly activeChildId = computed(() => {\n const match = this.routerUrl().match(/[?&]c=([^&]+)/);\n return match ? decodeURIComponent(match[1]) : null;\n });\n\n /** True when the current route starts with the given path. */\n isOnRoute(route: string): boolean {\n return this.routerUrl().startsWith(route);\n }\n\n /** Tailwind class string for a sidebar child nav item. */\n childItemClass(id: string): string {\n const base = 'flex w-full items-center rounded-md py-[5px] pl-3 pr-2 text-[12.5px] font-medium transition-colors duration-100';\n if (this.activeChildId() === id) {\n return `${base} bg-primary-50 text-primary-700 dark:bg-primary-950/70 dark:text-primary-300`;\n }\n return `${base} text-gray-500 hover:bg-gray-100/60 hover:text-gray-800 dark:text-gray-500 dark:hover:bg-gray-800/50 dark:hover:text-gray-300`;\n }\n\n /**\n * Returns the SVG path data for a given icon name.\n *\n * We use inline SVGs instead of an icon library to keep\n * the kit dependency-free. Each icon is a 24x24 Heroicon outline.\n */\n getIconPath(icon: string): string {\n const icons: Record<string, string> = {\n 'rocket':\n 'M9.315 7.584C12.195 3.883 16.695 1.5 21.75 1.5a.75.75 0 01.75.75c0 5.056-2.383 9.555-6.084 12.436A6.75 6.75 0 019.75 22.5a.75.75 0 01-.75-.75v-4.131A15.838 15.838 0 016.382 15H2.25a.75.75 0 01-.75-.75 6.75 6.75 0 017.815-6.666zM15 6.75a2.25 2.25 0 100 4.5 2.25 2.25 0 000-4.5zM5.26 17.242a.75.75 0 10-.897-1.203 5.243 5.243 0 00-2.05 5.022.75.75 0 00.625.627 5.243 5.243 0 005.022-2.051.75.75 0 10-1.202-.897 3.744 3.744 0 01-3.008 1.51c0-1.23.592-2.323 1.51-3.008z',\n 'book':\n 'M11.25 4.533A9.707 9.707 0 006 3a9.735 9.735 0 00-3.25.555.75.75 0 00-.5.707v14.25a.75.75 0 001 .707A8.237 8.237 0 016 18.75c1.995 0 3.823.707 5.25 1.886V4.533zM12.75 20.636A8.214 8.214 0 0118 18.75c.966 0 1.89.166 2.75.47a.75.75 0 001-.708V4.262a.75.75 0 00-.5-.707A9.735 9.735 0 0018 3a9.707 9.707 0 00-5.25 1.533v16.103z',\n 'chart-bar':\n 'M3 13h2v8H3zm6-4h2v12H9zm6-4h2v16h-2zm6 8h2v8h-2z',\n 'users':\n 'M16 11c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 3-1.34 3-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z',\n 'shopping-cart':\n 'M7 18c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM7.17 14.75l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.86-7.01L19.42 4h-.01l-1.1 2-2.76 5H8.53l-.13-.27L6.16 6l-.95-2-.94-2H1v2h2l3.6 7.59-1.35 2.44C5.52 15.37 6.48 17 8 17h12v-2H8l1.1-2h7.45z',\n 'cube':\n 'M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.36.2-.8.2-1.14 0l-7.9-4.44A.99.99 0 013 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.36-.2.8-.2 1.14 0l7.9 4.44c.32.17.53.5.53.88v9zM12 4.15L5 8.09v7.82l7 3.94 7-3.94V8.09l-7-3.94z',\n 'cog':\n 'M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1115.6 12 3.611 3.611 0 0112 15.6z',\n };\n return icons[icon] ?? '';\n }\n}","<!-- ============================================================\n Sidebar Navigation\n ============================================================\n Three states:\n 1. Desktop expanded — full width with labels\n 2. Desktop collapsed — narrow with icons only\n 3. Mobile overlay — slides in from left\n ============================================================ -->\n\n<aside\n class=\"fixed top-0 left-0 z-50 flex h-full flex-col border-r border-white/70 bg-white/78\n shadow-[0_24px_60px_-36px_rgba(15,23,42,0.85)] backdrop-blur-xl transition-all duration-300\n dark:border-gray-700/45 dark:bg-gray-900/70 dark:shadow-[0_26px_64px_-38px_rgba(2,6,23,0.95)]\"\n [class.w-64]=\"!collapsed()\"\n [class.w-20]=\"collapsed()\"\n [class.-translate-x-full]=\"!mobileOpen()\"\n [class.translate-x-0]=\"mobileOpen()\"\n [class.lg:translate-x-0]=\"true\"\n role=\"navigation\"\n aria-label=\"Main navigation\">\n\n <!-- ========== Logo Area (matches topbar height) ========== -->\n <div class=\"flex h-16 shrink-0 items-center border-b border-gray-200/80 px-3 dark:border-gray-800/60\"\n [class.justify-center]=\"collapsed()\">\n\n @if (!collapsed()) {\n <!-- Brand mark + wordmark -->\n <a routerLink=\"/components\" class=\"flex items-center gap-2.5\" aria-label=\"styloviz home\">\n <img src=\"logo-icon-sm.svg\" alt=\"styloviz logo\" class=\"h-8 w-auto shrink-0\" />\n <span class=\"text-[22px] font-semibold lowercase leading-none tracking-tight text-gray-900 dark:text-gray-100\">\n styloviz\n </span>\n </a>\n } @else {\n <!-- Brand mark only when collapsed -->\n <a routerLink=\"/components\" aria-label=\"styloviz home\" title=\"styloviz\">\n <img src=\"logo-icon-sm.svg\" alt=\"styloviz logo\" class=\"h-8 w-auto shrink-0\" />\n </a>\n }\n </div>\n\n <!-- ========== Navigation Menu ========== -->\n <nav class=\"flex-1 overflow-y-auto px-2.5 py-3\">\n @for (group of menuGroups(); track group.groupLabel) {\n <!-- Group Label (hidden when collapsed) -->\n @if (!collapsed()) {\n <p class=\"mb-1 mt-4 px-2.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400/80 dark:text-gray-600\">\n {{ group.groupLabel }}\n </p>\n } @else {\n <!-- Divider between groups in collapsed mode -->\n <div class=\"my-2.5 border-t border-gray-200/80 dark:border-gray-800/60\"></div>\n }\n\n <!-- Menu Items -->\n @for (item of group.items; track item.route) {\n <a\n [routerLink]=\"item.route\"\n routerLinkActive=\"bg-primary-50 text-primary-700 font-semibold dark:bg-primary-950/70 dark:text-primary-300\"\n [routerLinkActiveOptions]=\"{ exact: false }\"\n class=\"group mb-0.5 flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-[13.5px] font-medium\n text-gray-600 transition-colors duration-100\n hover:bg-gray-100/80 hover:text-gray-900\n dark:text-gray-400 dark:hover:bg-gray-800/70 dark:hover:text-gray-100\"\n [class.justify-center]=\"collapsed()\"\n [attr.title]=\"collapsed() ? item.label : null\"\n (click)=\"mobileClose.emit()\">\n\n <!-- Icon -->\n <svg\n class=\"h-[18px] w-[18px] shrink-0 opacity-80\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path [attr.d]=\"getIconPath(item.icon)\" />\n </svg>\n\n <!-- Label (hidden when collapsed) -->\n @if (!collapsed()) {\n <span class=\"flex-1 tracking-tight\">{{ item.label }}</span>\n\n <!-- Badge -->\n @if (item.badge) {\n <span class=\"rounded-full bg-primary-100 px-1.5 py-0.5 text-[10px] font-semibold text-primary-700\n dark:bg-primary-900/50 dark:text-primary-300\">\n {{ item.badge }}\n </span>\n }\n }\n </a>\n\n <!-- ── Sub-navigation (shown when expanded and on this route) ── -->\n @if (!collapsed() && item.childGroups && isOnRoute(item.route)) {\n <div class=\"mb-1 ml-[1.125rem] border-l border-gray-200/70 pl-3 dark:border-gray-800/50\">\n @for (childGroup of item.childGroups; track childGroup.label) {\n <!-- Sub-group label -->\n <p class=\"mb-0.5 px-1 text-[9.5px] font-semibold uppercase tracking-widest\n text-gray-400/70 dark:text-gray-700\"\n [class.mt-3]=\"!$first\"\n [class.mt-1.5]=\"$first\">\n {{ childGroup.label }}\n </p>\n <!-- Sub-items -->\n @for (child of childGroup.items; track child.id) {\n <a\n [routerLink]=\"[item.route]\"\n [queryParams]=\"{ c: child.id }\"\n [attr.data-nav-item]=\"child.id\"\n [class]=\"childItemClass(child.id)\"\n [attr.aria-current]=\"activeChildId() === child.id ? 'page' : null\"\n (click)=\"mobileClose.emit()\">\n {{ child.label }}\n </a>\n }\n }\n </div>\n }\n }\n }\n </nav>\n\n <!-- ========== Sidebar Footer (pinned) ========== -->\n <div class=\"shrink-0 border-t border-gray-200/80 px-2.5 py-2.5 dark:border-gray-800/60\">\n @if (!collapsed()) {\n <!-- Resources -->\n <div class=\"flex items-center justify-between rounded-lg bg-gray-50/70 px-1.5 py-1.5 dark:bg-gray-800/40\">\n <div class=\"flex items-center gap-0.5\">\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Documentation\" aria-label=\"Documentation\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M11.25 4.533A9.707 9.707 0 006 3a9.735 9.735 0 00-3.25.555.75.75 0 00-.5.707v14.25a.75.75 0 001 .707A8.237 8.237 0 016 18.75c1.995 0 3.823.707 5.25 1.886V4.533zM12.75 20.636A8.214 8.214 0 0118 18.75c.966 0 1.89.166 2.75.47a.75.75 0 001-.708V4.262a.75.75 0 00-.5-.707A9.735 9.735 0 0018 3a9.707 9.707 0 00-5.25 1.533v16.103z\" />\n </svg>\n </a>\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"GitHub\" aria-label=\"GitHub\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n <a [href]=\"npmUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"npm\" aria-label=\"npm package\"\n class=\"inline-flex h-7 items-center justify-center rounded-md px-1.5 font-mono text-[10px] font-bold\n text-gray-400 transition-colors hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n npm\n </a>\n <a [href]=\"communityUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Community\" aria-label=\"Community forum\"\n class=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-400 transition-colors\n hover:bg-white hover:text-primary-600 hover:shadow-sm\n dark:hover:bg-gray-700 dark:hover:text-primary-300\">\n <svg class=\"h-[15px] w-[15px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4.913 2.658c2.075-.27 4.19-.408 6.337-.408 2.147 0 4.262.139 6.337.408 1.922.25 3.291 1.861 3.405 3.727a4.403 4.403 0 00-1.032-.211 50.89 50.89 0 00-8.42 0c-2.358.196-4.04 2.19-4.04 4.434v4.286a4.47 4.47 0 002.433 3.984L7.28 21.53A.75.75 0 016 21v-4.03a48.527 48.527 0 01-1.087-.128C2.905 16.58 1.5 14.833 1.5 12.862V6.638c0-1.97 1.405-3.718 3.413-3.979z\" />\n <path d=\"M15.75 7.5c-1.376 0-2.739.057-4.086.169C10.124 7.797 9 9.103 9 10.609v4.285c0 1.507 1.128 2.814 2.67 2.94 1.243.102 2.5.157 3.768.165l2.782 2.781a.75.75 0 001.28-.53v-2.39l.33-.026c1.542-.125 2.67-1.433 2.67-2.94v-4.286c0-1.505-1.125-2.811-2.664-2.94A49.392 49.392 0 0015.75 7.5z\" />\n </svg>\n </a>\n </div>\n <span class=\"pr-1 text-[10px] font-semibold text-gray-400 dark:text-gray-600\">v1.0</span>\n </div>\n } @else {\n <!-- Collapsed: GitHub quick link -->\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"GitHub\" aria-label=\"GitHub\"\n class=\"flex items-center justify-center rounded-lg p-2 text-gray-400 transition-colors\n hover:bg-gray-100/80 hover:text-primary-600\n dark:text-gray-500 dark:hover:bg-gray-800/70 dark:hover:text-primary-300\">\n <svg class=\"h-[18px] w-[18px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n }\n </div>\n</aside>","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { ThemeService } from '@styloviz/core';\n\n/** A single external resource link rendered in the topbar / sidebar. */\nexport interface ResourceLink {\n readonly label: string;\n readonly href: string;\n}\n\n/**\n * TopbarComponent\n *\n * Slim, premium top navigation bar:\n * - Sidebar collapse (desktop) / hamburger (mobile)\n * - Resource links (Docs, Community, Pricing)\n * - GitHub + npm quick links\n * - Dark / light theme toggle\n * - \"Get Pro\" call-to-action\n */\n@Component({\n selector: 'sv-topbar',\n standalone: true,\n imports: [RouterLink],\n templateUrl: './topbar.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '(document:keydown.escape)': 'menuOpen.set(false)',\n '(document:pointerdown)': 'onDocumentPointerDown($event)',\n },\n})\nexport class SvTopbarComponent {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** Whether the small-screen navigation (hamburger) menu is open. */\n protected readonly menuOpen = signal(false);\n\n /** Whether the sidebar is currently collapsed (controls toggle icon rotation). */\n readonly sidebarCollapsed = input<boolean>(false);\n\n /** Overview / landing page. Internal routes use SPA navigation. */\n readonly overviewUrl = input<string>('/get-started');\n\n /** Documentation page. Internal routes ('/docs') use SPA navigation; an\n * external URL ('https://…') opens in a new tab. */\n readonly docsUrl = input<string>('/docs');\n\n /** Community / forum URL. */\n readonly communityUrl = input<string>('#community');\n\n /** Pricing page — internal route by default; also used by the \"Get Pro\" CTA. */\n readonly pricingUrl = input<string>('/pricing');\n\n /** Shared styling for the inline text resource links. */\n protected readonly resourceLinkClass =\n 'rounded-lg px-2.5 py-1.5 text-[13px] font-medium text-gray-600 transition-colors ' +\n 'hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white';\n\n /** Shared styling for the small-screen overflow-menu items. */\n protected readonly menuItemClass =\n 'block rounded-lg px-2.5 py-2 text-[13px] font-medium text-gray-600 transition-colors ' +\n 'hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white';\n\n /** True for in-app routes (start with '/') — rendered with routerLink. */\n protected isInternal(url: string): boolean {\n return url.startsWith('/');\n }\n\n protected toggleMenu(): void {\n this.menuOpen.update((open) => !open);\n }\n\n protected onDocumentPointerDown(event: PointerEvent): void {\n if (this.menuOpen() && !this.host.nativeElement.contains(event.target as Node)) {\n this.menuOpen.set(false);\n }\n }\n\n /**\n * Whether to render the built-in \"Get Pro\" CTA. Set false when the host\n * projects its own Pro control into the [topbar-actions] slot (avoids a\n * duplicate button). Defaults true for standalone library use.\n */\n readonly showGetPro = input<boolean>(true);\n\n /** GitHub repository URL. */\n readonly githubUrl = input<string>('#github');\n\n /** npm package URL. */\n readonly npmUrl = input<string>('#npm');\n\n // ── Outputs ────────────────────────────────────────────────────────────────\n\n /** Emits when the desktop sidebar toggle is clicked. */\n readonly toggleSidebar = output<void>();\n\n /** Emits when the mobile hamburger menu is clicked. */\n readonly toggleMobileSidebar = output<void>();\n\n // ── Services ───────────────────────────────────────────────────────────────\n\n /** @internal Theme service — used by the template's dark-mode toggle. */\n protected readonly themeService = inject(ThemeService);\n}\n","<!-- ============================================================\n Topbar Navigation\n ============================================================ -->\n\n<header\n class=\"sticky top-0 z-30 flex h-16 items-center gap-2 border-b border-white/70 bg-white/72\n px-4 shadow-[0_10px_26px_-22px_rgba(15,23,42,0.65)] backdrop-blur-xl dark:border-gray-700/45\n dark:bg-gray-900/62 dark:shadow-[0_14px_34px_-24px_rgba(2,6,23,0.95)] sm:px-5\">\n\n <!-- ========== Left: Menu Toggles ========== -->\n <div class=\"flex shrink-0 items-center gap-1.5\">\n <!-- Mobile hamburger (opens the sidebar) -->\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200 lg:hidden\"\n (click)=\"toggleMobileSidebar.emit()\"\n aria-label=\"Open sidebar\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n\n <!-- Desktop sidebar toggle -->\n <button\n type=\"button\"\n class=\"group hidden items-center justify-center rounded-lg border border-gray-200/80 bg-white/60 p-2\n text-gray-500 shadow-sm transition-all hover:border-gray-300 hover:bg-white hover:text-gray-800\n active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500\n focus-visible:ring-offset-1 dark:border-gray-700/70 dark:bg-gray-800/50 dark:text-gray-400\n dark:hover:border-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-100\n dark:focus-visible:ring-offset-gray-900 lg:inline-flex\"\n (click)=\"toggleSidebar.emit()\"\n [attr.aria-label]=\"sidebarCollapsed() ? 'Expand sidebar' : 'Collapse sidebar'\"\n [attr.aria-pressed]=\"!sidebarCollapsed()\"\n [attr.title]=\"sidebarCollapsed() ? 'Expand sidebar' : 'Collapse sidebar'\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.6\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"4.5\" width=\"18\" height=\"15\" rx=\"2.5\" />\n <path stroke-linecap=\"round\" d=\"M9 4.5v15\" />\n <path class=\"text-primary-500\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.8\"\n [attr.d]=\"sidebarCollapsed() ? 'M13.5 9.5l2.5 2.5-2.5 2.5' : 'M16 9.5L13.5 12l2.5 2.5'\" />\n </svg>\n </button>\n </div>\n\n <!-- ========== Center: Search (host-projected) ========== -->\n <div class=\"min-w-0 flex-1\">\n <div class=\"max-w-md\">\n <ng-content select=\"[topbar-search]\"></ng-content>\n </div>\n </div>\n\n <!-- ========== Right: Nav + Actions ========== -->\n <div class=\"flex shrink-0 items-center gap-1 sm:gap-1.5\">\n\n <!-- Primary nav (inline on large screens) -->\n <nav class=\"hidden items-center lg:flex\" aria-label=\"Primary\">\n @if (isInternal(overviewUrl())) {\n <a [routerLink]=\"overviewUrl()\" [class]=\"resourceLinkClass\">Overview</a>\n } @else {\n <a [href]=\"overviewUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Overview</a>\n }\n @if (isInternal(docsUrl())) {\n <a [routerLink]=\"docsUrl()\" [class]=\"resourceLinkClass\">Docs</a>\n } @else {\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Docs</a>\n }\n @if (isInternal(pricingUrl())) {\n <a [routerLink]=\"pricingUrl()\" [class]=\"resourceLinkClass\">Pricing</a>\n } @else {\n <a [href]=\"pricingUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" [class]=\"resourceLinkClass\">Pricing</a>\n }\n </nav>\n\n <span class=\"mx-1 hidden h-5 w-px bg-gray-200/90 dark:bg-gray-700/70 lg:block\"></span>\n\n <!-- GitHub (large screens) -->\n <a\n [href]=\"githubUrl()\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"hidden rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white lg:inline-flex\"\n aria-label=\"View on GitHub\">\n <svg class=\"h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n </svg>\n </a>\n\n <!-- npm (large screens) -->\n <a\n [href]=\"npmUrl()\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"hidden items-center rounded-md border border-gray-200/90 px-1.5 py-1 font-mono text-[11px]\n font-semibold text-gray-500 transition-colors hover:border-primary-300 hover:text-primary-600\n dark:border-gray-700/80 dark:text-gray-400 dark:hover:border-primary-700 dark:hover:text-primary-300\n lg:inline-flex\"\n aria-label=\"View on npm\">\n npm\n </a>\n\n <!-- Theme Toggle (always visible) -->\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700\n focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200\"\n (click)=\"themeService.toggleTheme()\"\n [attr.aria-label]=\"themeService.isDark() ? 'Switch to light mode' : 'Switch to dark mode'\">\n @if (themeService.isDark()) {\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z\" />\n </svg>\n } @else {\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\"\n d=\"M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z\" />\n </svg>\n }\n </button>\n\n <!-- Host-projected actions (e.g. Pro license status control) -->\n <ng-content select=\"[topbar-actions]\"></ng-content>\n\n <!-- Get Pro CTA (large screens; hidden when the host projects its own control) -->\n @if (showGetPro()) {\n <a\n [href]=\"pricingUrl()\"\n [attr.target]=\"isInternal(pricingUrl()) ? null : '_blank'\"\n rel=\"noopener noreferrer\"\n class=\"ml-0.5 hidden items-center gap-1.5 rounded-lg bg-gradient-to-r from-primary-600 to-cyan-500\n px-3.5 py-2 text-[13px] font-semibold text-white shadow-sm transition-all\n hover:from-primary-700 hover:to-cyan-600 hover:shadow-md\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:focus-visible:ring-offset-gray-900 lg:inline-flex\">\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.562.562 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.562.562 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z\" />\n </svg>\n <span class=\"hidden sm:inline\">Get Pro</span>\n </a>\n }\n\n <!-- ========== Overflow menu (small screens) ========== -->\n <div class=\"relative lg:hidden\">\n <button\n type=\"button\"\n (click)=\"toggleMenu()\"\n [attr.aria-expanded]=\"menuOpen()\"\n aria-label=\"More navigation\"\n class=\"flex items-center justify-center rounded-lg border border-gray-200/80 bg-white/60 p-2 text-gray-500\n shadow-sm transition-all hover:border-gray-300 hover:bg-white hover:text-gray-800 active:scale-95\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1\n dark:border-gray-700/70 dark:bg-gray-800/50 dark:text-gray-400 dark:hover:border-gray-600\n dark:hover:bg-gray-800 dark:hover:text-gray-100 dark:focus-visible:ring-offset-gray-900\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.7\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n\n @if (menuOpen()) {\n <div class=\"absolute right-0 top-[calc(100%+0.6rem)] z-40 w-56 overflow-hidden rounded-xl border\n border-gray-200/70 bg-white/95 p-1.5 shadow-[0_20px_50px_-20px_rgba(15,23,42,0.5)]\n ring-1 ring-black/[0.03] backdrop-blur-xl dark:border-gray-700/50 dark:bg-gray-900/95\n dark:ring-white/[0.04]\"\n role=\"menu\">\n <p class=\"px-2.5 pb-1 pt-1.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-600\">\n Navigation\n </p>\n @if (isInternal(overviewUrl())) {\n <a [routerLink]=\"overviewUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Overview</a>\n } @else {\n <a [href]=\"overviewUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Overview</a>\n }\n @if (isInternal(docsUrl())) {\n <a [routerLink]=\"docsUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Docs</a>\n } @else {\n <a [href]=\"docsUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Docs</a>\n }\n @if (isInternal(pricingUrl())) {\n <a [routerLink]=\"pricingUrl()\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Pricing</a>\n } @else {\n <a [href]=\"pricingUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Pricing</a>\n }\n\n <div class=\"my-1 border-t border-gray-100 dark:border-gray-800\"></div>\n\n <a [href]=\"githubUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">GitHub</a>\n <a [href]=\"npmUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">npm</a>\n <a [href]=\"communityUrl()\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"menuOpen.set(false)\" [class]=\"menuItemClass\" role=\"menuitem\">Community</a>\n </div>\n }\n </div>\n </div>\n</header>\n","import { ChangeDetectionStrategy, Component } from '@angular/core';\n\n/**\n * FooterComponent\n *\n * Simple dashboard footer with copyright and version info.\n * Sticks to the bottom of the content area (not the viewport).\n */\n@Component({\n selector: 'sv-footer',\n standalone: true,\n imports: [],\n templateUrl: './footer.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SvFooterComponent {\n readonly currentYear = new Date().getFullYear();\n}","<!-- ============================================================\n Footer\n ============================================================ -->\n\n<footer class=\"border-t border-white/65 bg-white/65 px-5 py-3 backdrop-blur-md dark:border-gray-700/40 dark:bg-gray-900/55\">\n <div class=\"mx-auto flex w-full max-w-[1440px] flex-col items-center justify-between gap-1 text-[11px] text-gray-500/80 dark:text-gray-500 sm:flex-row\">\n <p>&copy; {{ currentYear }} styloviz</p>\n <p>Angular 21 &amp; Tailwind CSS 4</p>\n </div>\n</footer>","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\n/**\n * PageHeaderComponent\n *\n * Reusable page title section with:\n * - Title and subtitle\n * - Optional description\n * - Content projection slot for action buttons\n *\n * Usage:\n * <sv-page-header\n * title=\"Analytics Dashboard\"\n * subtitle=\"Monitor your key metrics and performance.\">\n * <button class=\"...\">Export</button> <!-- projected into actions slot -->\n * </sv-page-header>\n */\n@Component({\n selector: 'sv-page-header',\n standalone: true,\n imports: [],\n templateUrl: './page-header.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SvPageHeaderComponent {\n /** Page title */\n title = input.required<string>();\n\n /** Optional subtitle / description */\n subtitle = input<string>('');\n}","<!-- ============================================================\n Page Header\n ============================================================ -->\n\n<div class=\"mb-7 flex flex-col gap-3 rounded-2xl border border-white/70 bg-white/75 px-5 py-4\n shadow-[0_14px_32px_-24px_rgba(15,23,42,0.55)] backdrop-blur-sm dark:border-gray-700/40\n dark:bg-gray-900/62 dark:shadow-[0_18px_38px_-28px_rgba(2,6,23,0.95)] sm:flex-row sm:items-center sm:justify-between\">\n <!-- Title area -->\n <div>\n <h1 class=\"text-xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-[1.35rem]\">\n {{ title() }}\n </h1>\n @if (subtitle()) {\n <p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">\n {{ subtitle() }}\n </p>\n }\n </div>\n\n <!-- Actions slot (projected content) -->\n <div class=\"flex items-center gap-2\">\n <ng-content />\n </div>\n</div>","import { ChangeDetectionStrategy, Component, signal } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\nimport { SvSidebarComponent } from '../sidebar/sidebar.component';\nimport { SvTopbarComponent } from '../topbar/topbar.component';\nimport { SvFooterComponent } from '../footer/footer.component';\nimport { SvToastHostComponent } from '@styloviz/toast';\n\n/**\n * DashboardShellComponent\n *\n * The master layout wrapper. Every dashboard page renders inside this shell.\n * It composes the sidebar, topbar, main content area, and footer.\n *\n * Layout structure:\n * ┌──────────┬────────────────────────────────┐\n * │ │ Topbar │\n * │ Sidebar ├────────────────────────────────┤\n * │ │ <router-outlet> (page content) │\n * │ ├────────────────────────────────┤\n * │ │ Footer │\n * └──────────┴────────────────────────────────┘\n */\n@Component({\n selector: 'sv-dashboard-shell',\n standalone: true,\n imports: [RouterOutlet, SvSidebarComponent, SvTopbarComponent, SvFooterComponent, SvToastHostComponent],\n templateUrl: './dashboard-shell.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SvDashboardShellComponent {\n /** Demo user displayed in the topbar. In a real app this comes from an auth service. */\n readonly currentUser = { name: 'Alex Johnson', email: 'alex@example.com', avatar: 'https://i.pravatar.cc/150?img=12' };\n\n /**\n * Controls whether the sidebar is collapsed (icons only) or expanded.\n * Signal makes this reactive — topbar toggle button and sidebar both stay in sync.\n */\n sidebarCollapsed = signal<boolean>(false);\n\n /**\n * Controls mobile sidebar visibility.\n * On mobile, the sidebar is hidden by default and slides in as an overlay.\n */\n mobileSidebarOpen = signal<boolean>(false);\n\n /**\n * Toggle sidebar between collapsed and expanded states (desktop).\n */\n toggleSidebar(): void {\n this.sidebarCollapsed.update((collapsed) => !collapsed);\n }\n\n /**\n * Toggle mobile sidebar overlay.\n */\n toggleMobileSidebar(): void {\n this.mobileSidebarOpen.update((open) => !open);\n }\n\n /**\n * Close mobile sidebar (e.g., after navigation or backdrop click).\n */\n closeMobileSidebar(): void {\n this.mobileSidebarOpen.set(false);\n }\n}","<!-- ============================================================\n Dashboard Shell Layout\n ============================================================\n Desktop: sidebar on left, content on right\n Mobile: sidebar hidden, hamburger menu in topbar\n ============================================================ -->\n\n<div class=\"relative flex h-screen overflow-hidden bg-[linear-gradient(180deg,#f8fafc_0%,#eef2ff_48%,#ecfeff_100%)] dark:bg-[linear-gradient(180deg,#020617_0%,#0f172a_52%,#111827_100%)]\">\n\n <div class=\"pointer-events-none absolute inset-0\">\n <div class=\"absolute -right-24 -top-24 h-72 w-72 rounded-full bg-primary-300/25 blur-3xl dark:bg-primary-600/20\"></div>\n <div class=\"absolute bottom-[-8rem] left-[-6rem] h-80 w-80 rounded-full bg-emerald-300/20 blur-3xl dark:bg-emerald-500/15\"></div>\n </div>\n\n <!-- ========== Mobile Sidebar Backdrop ========== -->\n @if (mobileSidebarOpen()) {\n <div\n class=\"fixed inset-0 z-40 bg-black/50 backdrop-blur-sm lg:hidden\"\n (click)=\"closeMobileSidebar()\"\n aria-hidden=\"true\">\n </div>\n }\n\n <!-- ========== Sidebar ========== -->\n <sv-sidebar\n [collapsed]=\"sidebarCollapsed()\"\n [mobileOpen]=\"mobileSidebarOpen()\"\n (mobileClose)=\"closeMobileSidebar()\"\n (toggleCollapse)=\"toggleSidebar()\"\n />\n\n <!-- ========== Main Content Area ========== -->\n <div\n class=\"relative z-10 flex min-w-0 flex-1 flex-col overflow-hidden transition-all duration-300\"\n [class.lg:ml-64]=\"!sidebarCollapsed()\"\n [class.lg:ml-20]=\"sidebarCollapsed()\">\n\n <!-- Topbar -->\n <sv-topbar\n [sidebarCollapsed]=\"sidebarCollapsed()\"\n (toggleSidebar)=\"toggleSidebar()\"\n (toggleMobileSidebar)=\"toggleMobileSidebar()\"\n />\n\n <!-- Page Content -->\n <main class=\"flex-1 overflow-y-auto px-4 pb-8 pt-5 sm:px-6 sm:pt-6 lg:px-8 lg:pt-7\">\n <div class=\"mx-auto w-full max-w-[1440px]\">\n <router-outlet />\n </div>\n </main>\n\n <!-- Footer -->\n <sv-footer />\n </div>\n\n <!-- Global toast stacks — one host per position, each filters its own toasts -->\n <sv-toast-host position=\"top-left\" />\n <sv-toast-host position=\"top-center\" />\n <sv-toast-host position=\"top-right\" />\n <sv-toast-host position=\"bottom-left\" />\n <sv-toast-host position=\"bottom-center\" />\n <sv-toast-host position=\"bottom-right\" />\n</div>","// ─── Sidebar ──────────────────────────────────────────────────────────────────\nexport { SvSidebarComponent } from './sidebar/sidebar.component';\n\n// ─── Topbar ───────────────────────────────────────────────────────────────────\nexport { SvTopbarComponent } from './topbar/topbar.component';\nexport type { ResourceLink } from './topbar/topbar.component';\n\n// ─── Footer ───────────────────────────────────────────────────────────────────\nexport { SvFooterComponent } from './footer/footer.component';\n\n// ─── Page Header ──────────────────────────────────────────────────────────────\nexport { SvPageHeaderComponent } from './page-header/page-header.component';\n\n// ─── Dashboard Shell ──────────────────────────────────────────────────────────\nexport { SvDashboardShellComponent } from './dashboard-shell/dashboard-shell.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@styloviz/layout",
3
+ "version": "0.1.1",
4
+ "description": "Responsive dashboard layout primitives — app shell, sidebar, header and content grid.",
5
+ "keywords": [
6
+ "angular",
7
+ "tailwind",
8
+ "layout",
9
+ "sidebar",
10
+ "dashboard",
11
+ "styloviz"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "sazzad-bs23",
15
+ "homepage": "https://styloviz.dev",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/rhossain/angular-tailwind-dashboard-components.git",
19
+ "directory": "projects/layout"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/rhossain/angular-tailwind-dashboard-components/issues"
23
+ },
24
+ "peerDependencies": {
25
+ "@angular/common": ">=21.0.0",
26
+ "@angular/core": ">=21.0.0",
27
+ "@angular/router": ">=21.0.0",
28
+ "@styloviz/core": ">=0.1.0",
29
+ "@styloviz/toast": ">=0.1.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "engines": {
35
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
36
+ },
37
+ "sideEffects": false,
38
+ "module": "fesm2022/styloviz-layout.mjs",
39
+ "typings": "types/styloviz-layout.d.ts",
40
+ "exports": {
41
+ "./package.json": {
42
+ "default": "./package.json"
43
+ },
44
+ ".": {
45
+ "types": "./types/styloviz-layout.d.ts",
46
+ "default": "./fesm2022/styloviz-layout.mjs"
47
+ }
48
+ },
49
+ "type": "module",
50
+ "dependencies": {
51
+ "tslib": "^2.3.0"
52
+ }
53
+ }
@@ -0,0 +1,209 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { MenuGroup, ThemeService } from '@styloviz/core';
3
+
4
+ /**
5
+ * SidebarComponent
6
+ *
7
+ * Collapsible navigation sidebar with grouped menu items.
8
+ *
9
+ * Features:
10
+ * - Expanded mode: shows icons + labels + group headings
11
+ * - Collapsed mode: shows icons only with tooltip-style labels
12
+ * - Mobile mode: slides in as overlay from the left
13
+ * - Active route highlighting via RouterLinkActive
14
+ * - Accessible keyboard navigation
15
+ */
16
+ declare class SvSidebarComponent {
17
+ /** Whether the sidebar is in collapsed (icon-only) mode */
18
+ collapsed: _angular_core.InputSignal<boolean>;
19
+ /** Whether the mobile sidebar overlay is open */
20
+ mobileOpen: _angular_core.InputSignal<boolean>;
21
+ /** Emits when the mobile sidebar should close */
22
+ mobileClose: _angular_core.OutputEmitterRef<void>;
23
+ /** Emits when the user clicks the collapse/expand toggle inside the sidebar */
24
+ toggleCollapse: _angular_core.OutputEmitterRef<void>;
25
+ /**
26
+ * Navigation menu groups injected by the parent shell.
27
+ * Defaults to the built-in NAVIGATION_ITEMS so the component
28
+ * works out-of-the-box, but buyers can pass their own structure.
29
+ */
30
+ menuGroups: _angular_core.InputSignal<MenuGroup[]>;
31
+ /** Documentation site URL. */
32
+ readonly docsUrl: _angular_core.InputSignal<string>;
33
+ /** GitHub repository URL. */
34
+ readonly githubUrl: _angular_core.InputSignal<string>;
35
+ /** npm package URL. */
36
+ readonly npmUrl: _angular_core.InputSignal<string>;
37
+ /** Community / forum URL. */
38
+ readonly communityUrl: _angular_core.InputSignal<string>;
39
+ private readonly router;
40
+ /** Host element — used to locate and center the active nav item. */
41
+ private readonly host;
42
+ constructor();
43
+ /** Reactive snapshot of the current URL, updated on every navigation. */
44
+ private readonly routerUrl;
45
+ /**
46
+ * ID of the active child item (read from ?c= query param), or `null` on a
47
+ * bare `/components` route — which shows the dashboard overview, so no child
48
+ * item should appear selected.
49
+ */
50
+ readonly activeChildId: _angular_core.Signal<string | null>;
51
+ /** True when the current route starts with the given path. */
52
+ isOnRoute(route: string): boolean;
53
+ /** Tailwind class string for a sidebar child nav item. */
54
+ childItemClass(id: string): string;
55
+ /**
56
+ * Returns the SVG path data for a given icon name.
57
+ *
58
+ * We use inline SVGs instead of an icon library to keep
59
+ * the kit dependency-free. Each icon is a 24x24 Heroicon outline.
60
+ */
61
+ getIconPath(icon: string): string;
62
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvSidebarComponent, never>;
63
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvSidebarComponent, "sv-sidebar", never, { "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "mobileOpen": { "alias": "mobileOpen"; "required": false; "isSignal": true; }; "menuGroups": { "alias": "menuGroups"; "required": false; "isSignal": true; }; "docsUrl": { "alias": "docsUrl"; "required": false; "isSignal": true; }; "githubUrl": { "alias": "githubUrl"; "required": false; "isSignal": true; }; "npmUrl": { "alias": "npmUrl"; "required": false; "isSignal": true; }; "communityUrl": { "alias": "communityUrl"; "required": false; "isSignal": true; }; }, { "mobileClose": "mobileClose"; "toggleCollapse": "toggleCollapse"; }, never, never, true, never>;
64
+ }
65
+
66
+ /** A single external resource link rendered in the topbar / sidebar. */
67
+ interface ResourceLink {
68
+ readonly label: string;
69
+ readonly href: string;
70
+ }
71
+ /**
72
+ * TopbarComponent
73
+ *
74
+ * Slim, premium top navigation bar:
75
+ * - Sidebar collapse (desktop) / hamburger (mobile)
76
+ * - Resource links (Docs, Community, Pricing)
77
+ * - GitHub + npm quick links
78
+ * - Dark / light theme toggle
79
+ * - "Get Pro" call-to-action
80
+ */
81
+ declare class SvTopbarComponent {
82
+ private readonly host;
83
+ /** Whether the small-screen navigation (hamburger) menu is open. */
84
+ protected readonly menuOpen: _angular_core.WritableSignal<boolean>;
85
+ /** Whether the sidebar is currently collapsed (controls toggle icon rotation). */
86
+ readonly sidebarCollapsed: _angular_core.InputSignal<boolean>;
87
+ /** Overview / landing page. Internal routes use SPA navigation. */
88
+ readonly overviewUrl: _angular_core.InputSignal<string>;
89
+ /** Documentation page. Internal routes ('/docs') use SPA navigation; an
90
+ * external URL ('https://…') opens in a new tab. */
91
+ readonly docsUrl: _angular_core.InputSignal<string>;
92
+ /** Community / forum URL. */
93
+ readonly communityUrl: _angular_core.InputSignal<string>;
94
+ /** Pricing page — internal route by default; also used by the "Get Pro" CTA. */
95
+ readonly pricingUrl: _angular_core.InputSignal<string>;
96
+ /** Shared styling for the inline text resource links. */
97
+ protected readonly resourceLinkClass: string;
98
+ /** Shared styling for the small-screen overflow-menu items. */
99
+ protected readonly menuItemClass: string;
100
+ /** True for in-app routes (start with '/') — rendered with routerLink. */
101
+ protected isInternal(url: string): boolean;
102
+ protected toggleMenu(): void;
103
+ protected onDocumentPointerDown(event: PointerEvent): void;
104
+ /**
105
+ * Whether to render the built-in "Get Pro" CTA. Set false when the host
106
+ * projects its own Pro control into the [topbar-actions] slot (avoids a
107
+ * duplicate button). Defaults true for standalone library use.
108
+ */
109
+ readonly showGetPro: _angular_core.InputSignal<boolean>;
110
+ /** GitHub repository URL. */
111
+ readonly githubUrl: _angular_core.InputSignal<string>;
112
+ /** npm package URL. */
113
+ readonly npmUrl: _angular_core.InputSignal<string>;
114
+ /** Emits when the desktop sidebar toggle is clicked. */
115
+ readonly toggleSidebar: _angular_core.OutputEmitterRef<void>;
116
+ /** Emits when the mobile hamburger menu is clicked. */
117
+ readonly toggleMobileSidebar: _angular_core.OutputEmitterRef<void>;
118
+ /** @internal Theme service — used by the template's dark-mode toggle. */
119
+ protected readonly themeService: ThemeService;
120
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvTopbarComponent, never>;
121
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvTopbarComponent, "sv-topbar", never, { "sidebarCollapsed": { "alias": "sidebarCollapsed"; "required": false; "isSignal": true; }; "overviewUrl": { "alias": "overviewUrl"; "required": false; "isSignal": true; }; "docsUrl": { "alias": "docsUrl"; "required": false; "isSignal": true; }; "communityUrl": { "alias": "communityUrl"; "required": false; "isSignal": true; }; "pricingUrl": { "alias": "pricingUrl"; "required": false; "isSignal": true; }; "showGetPro": { "alias": "showGetPro"; "required": false; "isSignal": true; }; "githubUrl": { "alias": "githubUrl"; "required": false; "isSignal": true; }; "npmUrl": { "alias": "npmUrl"; "required": false; "isSignal": true; }; }, { "toggleSidebar": "toggleSidebar"; "toggleMobileSidebar": "toggleMobileSidebar"; }, never, ["[topbar-search]", "[topbar-actions]"], true, never>;
122
+ }
123
+
124
+ /**
125
+ * FooterComponent
126
+ *
127
+ * Simple dashboard footer with copyright and version info.
128
+ * Sticks to the bottom of the content area (not the viewport).
129
+ */
130
+ declare class SvFooterComponent {
131
+ readonly currentYear: number;
132
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvFooterComponent, never>;
133
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvFooterComponent, "sv-footer", never, {}, {}, never, never, true, never>;
134
+ }
135
+
136
+ /**
137
+ * PageHeaderComponent
138
+ *
139
+ * Reusable page title section with:
140
+ * - Title and subtitle
141
+ * - Optional description
142
+ * - Content projection slot for action buttons
143
+ *
144
+ * Usage:
145
+ * <sv-page-header
146
+ * title="Analytics Dashboard"
147
+ * subtitle="Monitor your key metrics and performance.">
148
+ * <button class="...">Export</button> <!-- projected into actions slot -->
149
+ * </sv-page-header>
150
+ */
151
+ declare class SvPageHeaderComponent {
152
+ /** Page title */
153
+ title: _angular_core.InputSignal<string>;
154
+ /** Optional subtitle / description */
155
+ subtitle: _angular_core.InputSignal<string>;
156
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvPageHeaderComponent, never>;
157
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvPageHeaderComponent, "sv-page-header", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
158
+ }
159
+
160
+ /**
161
+ * DashboardShellComponent
162
+ *
163
+ * The master layout wrapper. Every dashboard page renders inside this shell.
164
+ * It composes the sidebar, topbar, main content area, and footer.
165
+ *
166
+ * Layout structure:
167
+ * ┌──────────┬────────────────────────────────┐
168
+ * │ │ Topbar │
169
+ * │ Sidebar ├────────────────────────────────┤
170
+ * │ │ <router-outlet> (page content) │
171
+ * │ ├────────────────────────────────┤
172
+ * │ │ Footer │
173
+ * └──────────┴────────────────────────────────┘
174
+ */
175
+ declare class SvDashboardShellComponent {
176
+ /** Demo user displayed in the topbar. In a real app this comes from an auth service. */
177
+ readonly currentUser: {
178
+ name: string;
179
+ email: string;
180
+ avatar: string;
181
+ };
182
+ /**
183
+ * Controls whether the sidebar is collapsed (icons only) or expanded.
184
+ * Signal makes this reactive — topbar toggle button and sidebar both stay in sync.
185
+ */
186
+ sidebarCollapsed: _angular_core.WritableSignal<boolean>;
187
+ /**
188
+ * Controls mobile sidebar visibility.
189
+ * On mobile, the sidebar is hidden by default and slides in as an overlay.
190
+ */
191
+ mobileSidebarOpen: _angular_core.WritableSignal<boolean>;
192
+ /**
193
+ * Toggle sidebar between collapsed and expanded states (desktop).
194
+ */
195
+ toggleSidebar(): void;
196
+ /**
197
+ * Toggle mobile sidebar overlay.
198
+ */
199
+ toggleMobileSidebar(): void;
200
+ /**
201
+ * Close mobile sidebar (e.g., after navigation or backdrop click).
202
+ */
203
+ closeMobileSidebar(): void;
204
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvDashboardShellComponent, never>;
205
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvDashboardShellComponent, "sv-dashboard-shell", never, {}, {}, never, never, true, never>;
206
+ }
207
+
208
+ export { SvDashboardShellComponent, SvFooterComponent, SvPageHeaderComponent, SvSidebarComponent, SvTopbarComponent };
209
+ export type { ResourceLink };