@ojiepermana/angular 0.1.1 → 21.0.0

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.
Files changed (92) hide show
  1. package/README.md +41 -249
  2. package/fesm2022/ojiepermana-angular-chart.mjs +3714 -0
  3. package/fesm2022/ojiepermana-angular-chart.mjs.map +1 -0
  4. package/fesm2022/ojiepermana-angular-component.mjs +3463 -0
  5. package/fesm2022/ojiepermana-angular-component.mjs.map +1 -0
  6. package/fesm2022/ojiepermana-angular-layout.mjs +276 -408
  7. package/fesm2022/ojiepermana-angular-layout.mjs.map +1 -1
  8. package/fesm2022/ojiepermana-angular-navigation.mjs +2198 -404
  9. package/fesm2022/ojiepermana-angular-navigation.mjs.map +1 -1
  10. package/fesm2022/ojiepermana-angular-theme.mjs +381 -1
  11. package/fesm2022/ojiepermana-angular-theme.mjs.map +1 -1
  12. package/fesm2022/ojiepermana-angular.mjs +15 -1
  13. package/fesm2022/ojiepermana-angular.mjs.map +1 -1
  14. package/package.json +49 -36
  15. package/theme/styles/etos.css +38 -0
  16. package/theme/styles/index.css +32 -8
  17. package/theme/styles/themes/brand/etos/color.css +21 -0
  18. package/theme/styles/themes/brand/etos/style.css +50 -0
  19. package/theme/styles/themes/library/_components.css +63 -0
  20. package/theme/styles/themes/library/_layers.css +15 -0
  21. package/theme/styles/themes/library/_material-overrides.css +254 -0
  22. package/theme/styles/themes/library/_tokens.css +54 -0
  23. package/theme/styles/themes/library/color/amber.css +18 -0
  24. package/theme/styles/themes/library/color/blue.css +23 -0
  25. package/theme/styles/themes/library/color/green.css +18 -0
  26. package/theme/styles/themes/library/color/index.css +9 -0
  27. package/theme/styles/themes/library/color/purple.css +18 -0
  28. package/theme/styles/themes/library/color/red.css +18 -0
  29. package/theme/styles/themes/library/style/brutal.css +47 -0
  30. package/theme/styles/themes/library/style/default.css +51 -0
  31. package/theme/styles/themes/library/style/index.css +8 -0
  32. package/theme/styles/themes/library/style/sharp.css +47 -0
  33. package/theme/styles/themes/library/style/soft.css +47 -0
  34. package/theme/styles/themes/mode/dark.css +20 -0
  35. package/theme/styles/themes/mode/index.css +6 -0
  36. package/theme/styles/themes/mode/light.css +24 -0
  37. package/theme/styles/themes/taildwind.css +109 -0
  38. package/types/ojiepermana-angular-chart.d.ts +1094 -0
  39. package/types/ojiepermana-angular-component.d.ts +1174 -0
  40. package/types/ojiepermana-angular-layout.d.ts +123 -76
  41. package/types/ojiepermana-angular-navigation.d.ts +256 -116
  42. package/types/ojiepermana-angular-theme.d.ts +170 -1
  43. package/types/ojiepermana-angular.d.ts +2 -1
  44. package/fesm2022/ojiepermana-angular-internal.mjs +0 -489
  45. package/fesm2022/ojiepermana-angular-internal.mjs.map +0 -1
  46. package/fesm2022/ojiepermana-angular-navigation-horizontal.mjs +0 -721
  47. package/fesm2022/ojiepermana-angular-navigation-horizontal.mjs.map +0 -1
  48. package/fesm2022/ojiepermana-angular-navigation-vertical.mjs +0 -1647
  49. package/fesm2022/ojiepermana-angular-navigation-vertical.mjs.map +0 -1
  50. package/fesm2022/ojiepermana-angular-shell.mjs +0 -19
  51. package/fesm2022/ojiepermana-angular-shell.mjs.map +0 -1
  52. package/fesm2022/ojiepermana-angular-theme-component.mjs +0 -235
  53. package/fesm2022/ojiepermana-angular-theme-component.mjs.map +0 -1
  54. package/fesm2022/ojiepermana-angular-theme-directive.mjs +0 -29
  55. package/fesm2022/ojiepermana-angular-theme-directive.mjs.map +0 -1
  56. package/fesm2022/ojiepermana-angular-theme-service.mjs +0 -241
  57. package/fesm2022/ojiepermana-angular-theme-service.mjs.map +0 -1
  58. package/layout/README.md +0 -144
  59. package/layout/src/component/horizontal/horizontal.css +0 -130
  60. package/layout/src/component/vertical/vertical.css +0 -75
  61. package/layout/src/layout.css +0 -16
  62. package/navigation/README.md +0 -301
  63. package/navigation/horizontal/README.md +0 -49
  64. package/shell/README.md +0 -41
  65. package/styles/index.css +0 -2
  66. package/styles/resets.css +0 -22
  67. package/theme/README.md +0 -379
  68. package/theme/styles/adapters/material-ui/index.css +0 -205
  69. package/theme/styles/modes/dark.css +0 -84
  70. package/theme/styles/presets/colors/blue.css +0 -45
  71. package/theme/styles/presets/colors/brand.css +0 -52
  72. package/theme/styles/presets/colors/cyan.css +0 -45
  73. package/theme/styles/presets/colors/green.css +0 -45
  74. package/theme/styles/presets/colors/index.css +0 -7
  75. package/theme/styles/presets/colors/orange.css +0 -45
  76. package/theme/styles/presets/colors/purple.css +0 -45
  77. package/theme/styles/presets/colors/red.css +0 -45
  78. package/theme/styles/presets/styles/flat.css +0 -61
  79. package/theme/styles/presets/styles/glass.css +0 -28
  80. package/theme/styles/presets/styles/index.css +0 -2
  81. package/theme/styles/roles/index.css +0 -67
  82. package/theme/styles/tokens/foundation.css +0 -136
  83. package/theme/styles/tokens/semantic.css +0 -87
  84. package/theme/styles/utilities/index.css +0 -88
  85. package/types/ojiepermana-angular-internal.d.ts +0 -90
  86. package/types/ojiepermana-angular-navigation-horizontal.d.ts +0 -81
  87. package/types/ojiepermana-angular-navigation-vertical.d.ts +0 -262
  88. package/types/ojiepermana-angular-shell.d.ts +0 -14
  89. package/types/ojiepermana-angular-theme-component.d.ts +0 -46
  90. package/types/ojiepermana-angular-theme-directive.d.ts +0 -10
  91. package/types/ojiepermana-angular-theme-service.d.ts +0 -68
  92. /package/{navigation/vertical → chart}/README.md +0 -0
@@ -1,472 +1,2266 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, isDevMode, makeEnvironmentProviders, inject, PLATFORM_ID, signal, Injectable, computed } from '@angular/core';
3
- import { DOCUMENT, isPlatformBrowser } from '@angular/common';
2
+ import { inject, DestroyRef, signal, computed, Injectable, PLATFORM_ID, input, ChangeDetectionStrategy, Component, ViewContainerRef, viewChild, effect, ElementRef } from '@angular/core';
3
+ import { Router, NavigationEnd, RouterLink, RouterLinkActive } from '@angular/router';
4
+ import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
5
+ import { filter, map } from 'rxjs/operators';
6
+ import { DOCUMENT, isPlatformBrowser, NgClass, NgTemplateOutlet } from '@angular/common';
7
+ import { MatTooltip } from '@angular/material/tooltip';
8
+ import { clsx } from 'clsx';
9
+ import { twMerge } from 'tailwind-merge';
10
+ import { BreakpointObserver } from '@angular/cdk/layout';
11
+ import { Overlay } from '@angular/cdk/overlay';
12
+ import { TemplatePortal } from '@angular/cdk/portal';
13
+ import { FocusTrapFactory } from '@angular/cdk/a11y';
14
+ import { merge } from 'rxjs';
4
15
 
5
- const DEFAULT_NG_NAVIGATION_CONFIG = {
6
- defaultHorizontalVariant: 'default',
7
- defaultVerticalVariant: 'default',
8
- };
9
- const NG_NAVIGATION_CONFIG = new InjectionToken('NG_NAVIGATION_CONFIG', {
10
- providedIn: 'root',
11
- factory: () => ({ ...DEFAULT_NG_NAVIGATION_CONFIG }),
12
- });
13
-
14
- function warnInvalidNavigationConfig(message) {
15
- if (isDevMode()) {
16
- console.warn(`[provideNgNavigation] ${message}`);
16
+ /** Default registry key used when no id is specified. */
17
+ const DEFAULT_NAVIGATION_ID = 'main';
18
+ /**
19
+ * Signal-based global state untuk navigation (sidebar/topbar).
20
+ *
21
+ * Items disimpan dalam registry ber-key. Key default adalah `'main'`.
22
+ * Komponen `ui-sidebar` / `ui-topbar` memilih registry via input `navigationId`.
23
+ */
24
+ class NavigationService {
25
+ router = inject(Router);
26
+ destroyRef = inject(DestroyRef);
27
+ /** Internal version counter — incremented on every registry mutation. */
28
+ _version = signal(0, ...(ngDevMode ? [{ debugName: "_version" }] : /* istanbul ignore next */ []));
29
+ /** Internal map of registered navigation trees. */
30
+ _registry = new Map();
31
+ /**
32
+ * Backward-compatible accessor — returns items for the default (`'main'`) key.
33
+ * Prefer `getItems(id)` when working with named registries.
34
+ */
35
+ items = computed(() => this.getItems(DEFAULT_NAVIGATION_ID)(), ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
36
+ /** Sidebar collapsed (default ↔ thin) toggle untuk desktop. */
37
+ collapsed = signal(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
38
+ /** Sheet drawer terbuka di mobile. */
39
+ mobileOpen = signal(false, ...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
40
+ /** Set id grup / collapsable yang sedang terbuka. */
41
+ openGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "openGroups" }] : /* istanbul ignore next */ []));
42
+ /** URL aktif terakhir. Update otomatis dari Router `NavigationEnd`. */
43
+ activeUrl = signal(this.router.url, ...(ngDevMode ? [{ debugName: "activeUrl" }] : /* istanbul ignore next */ []));
44
+ /** Trail id item yang sedang match dengan URL aktif (across ALL registries). */
45
+ activeTrail = computed(() => {
46
+ this._version(); // track changes
47
+ const url = this.activeUrl();
48
+ const trail = new Set();
49
+ const walk = (list, ancestors) => {
50
+ let matched = false;
51
+ for (const item of list) {
52
+ const id = item.id;
53
+ const link = 'link' in item ? item.link : undefined;
54
+ let selfMatch = false;
55
+ if (link) {
56
+ selfMatch = url === link || url.startsWith(link + '/') || url.startsWith(link + '?');
57
+ }
58
+ const children = 'children' in item ? (item.children ?? []) : [];
59
+ const nextAncestors = id ? [...ancestors, id] : ancestors;
60
+ const childMatch = children.length > 0 && walk(children, nextAncestors);
61
+ if (selfMatch || childMatch) {
62
+ if (id)
63
+ trail.add(id);
64
+ for (const a of ancestors)
65
+ trail.add(a);
66
+ matched = true;
67
+ }
68
+ }
69
+ return matched;
70
+ };
71
+ for (const items of this._registry.values()) {
72
+ walk(items, []);
73
+ }
74
+ return trail;
75
+ }, ...(ngDevMode ? [{ debugName: "activeTrail" }] : /* istanbul ignore next */ []));
76
+ constructor() {
77
+ this.router.events
78
+ .pipe(filter((e) => e instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
79
+ .subscribe((e) => this.activeUrl.set(e.urlAfterRedirects));
17
80
  }
18
- }
19
- function isHorizontalNavigationVariant$1(value) {
20
- return value === 'default' || value === 'mega';
21
- }
22
- function isVerticalNavigationAppearance$1(value) {
23
- return value === 'default' || value === 'collapsible';
24
- }
25
- function normalizeHorizontalVariant(value) {
26
- if (typeof value === 'undefined' || isHorizontalNavigationVariant$1(value)) {
27
- return value ?? DEFAULT_NG_NAVIGATION_CONFIG.defaultHorizontalVariant;
81
+ registerItems(idOrItems, maybeItems) {
82
+ const [id, items] = typeof idOrItems === 'string' ? [idOrItems, maybeItems] : [DEFAULT_NAVIGATION_ID, idOrItems];
83
+ this._registry.set(id, items);
84
+ this._version.update((v) => v + 1);
28
85
  }
29
- warnInvalidNavigationConfig(`Ignoring invalid defaultHorizontalVariant ${JSON.stringify(value)}. Falling back to ${JSON.stringify(DEFAULT_NG_NAVIGATION_CONFIG.defaultHorizontalVariant)}.`);
30
- return DEFAULT_NG_NAVIGATION_CONFIG.defaultHorizontalVariant;
31
- }
32
- function normalizeVerticalVariant(value) {
33
- if (typeof value === 'undefined' || isVerticalNavigationAppearance$1(value)) {
34
- return value ?? DEFAULT_NG_NAVIGATION_CONFIG.defaultVerticalVariant;
86
+ /** Remove a named registry entry. */
87
+ removeItems(id) {
88
+ this._registry.delete(id);
89
+ this._version.update((v) => v + 1);
35
90
  }
36
- warnInvalidNavigationConfig(`Ignoring invalid defaultVerticalVariant ${JSON.stringify(value)}. Falling back to ${JSON.stringify(DEFAULT_NG_NAVIGATION_CONFIG.defaultVerticalVariant)}.`);
37
- return DEFAULT_NG_NAVIGATION_CONFIG.defaultVerticalVariant;
38
- }
39
- function normalizeNavigationConfig(config) {
40
- return {
41
- ...DEFAULT_NG_NAVIGATION_CONFIG,
42
- defaultHorizontalVariant: normalizeHorizontalVariant(config.defaultHorizontalVariant),
43
- defaultVerticalVariant: normalizeVerticalVariant(config.defaultVerticalVariant),
44
- };
45
- }
46
- function provideNgNavigation(config = {}) {
47
- return makeEnvironmentProviders([{ provide: NG_NAVIGATION_CONFIG, useValue: normalizeNavigationConfig(config) }]);
48
- }
49
-
50
- function getNavigationItemAction(item) {
51
- return isNavigationRoutableItem(item) ? item.action : undefined;
52
- }
53
- function getNavigationItemVisibilityHandler(item) {
54
- return item.isHidden;
55
- }
56
- function isNavigationItemHidden(item) {
57
- return !!getNavigationItemVisibilityHandler(item)?.(item);
58
- }
59
- function shouldRenderNavigationItem(item) {
60
- return !isNavigationItemHidden(item);
61
- }
62
- function hasNavigationChildren(item) {
63
- return Array.isArray(item.children) && item.children.length > 0;
64
- }
65
- function getNavigationChildren(item) {
66
- return Array.isArray(item.children) ? item.children : [];
67
- }
68
- function isNavigationRoutableItem(item) {
69
- return item.type === 'basic' || item.type === 'aside' || item.type === 'collapsable';
70
- }
71
-
72
- class NavigationStorageAdapter {
73
- config;
74
- constructor(config) {
75
- this.config = config;
91
+ /**
92
+ * Computed yang mengembalikan items untuk key tertentu.
93
+ * Reactive terhadap perubahan registry.
94
+ */
95
+ getItems(id) {
96
+ return computed(() => {
97
+ this._version(); // track changes
98
+ return this._registry.get(id) ?? [];
99
+ });
76
100
  }
77
- clear(axis) {
78
- if (!this.config.isBrowser || !this.config.storage) {
79
- return;
80
- }
81
- this.config.storage.removeItem(this.key(axis));
82
- this.config.storage.removeItem(this.legacyKey(axis));
101
+ /** Toggle sidebar collapsed (default ↔ thin). */
102
+ toggleCollapsed() {
103
+ this.collapsed.update((v) => !v);
83
104
  }
84
- persist(axis, value) {
85
- if (!this.config.isBrowser || !this.config.storage) {
86
- return;
87
- }
88
- this.config.storage.setItem(this.key(axis), value);
89
- this.config.storage.removeItem(this.legacyKey(axis));
105
+ setCollapsed(value) {
106
+ this.collapsed.set(value);
90
107
  }
91
- read(axis, fallback, isValid) {
92
- if (!this.config.isBrowser || !this.config.storage) {
93
- return fallback;
94
- }
95
- const storedValue = this.config.storage.getItem(this.key(axis));
96
- if (storedValue && isValid(storedValue)) {
97
- return storedValue;
98
- }
99
- const legacyValue = this.config.storage.getItem(this.legacyKey(axis));
100
- if (legacyValue && isValid(legacyValue)) {
101
- this.config.storage.setItem(this.key(axis), legacyValue);
102
- this.config.storage.removeItem(this.legacyKey(axis));
103
- return legacyValue;
104
- }
105
- return fallback;
108
+ openMobile() {
109
+ this.mobileOpen.set(true);
106
110
  }
107
- readValue(axis) {
108
- if (!this.config.isBrowser || !this.config.storage) {
109
- return null;
110
- }
111
- const storedValue = this.config.storage.getItem(this.key(axis));
112
- if (storedValue !== null) {
113
- return storedValue;
114
- }
115
- const legacyValue = this.config.storage.getItem(this.legacyKey(axis));
116
- if (legacyValue !== null) {
117
- this.config.storage.setItem(this.key(axis), legacyValue);
118
- this.config.storage.removeItem(this.legacyKey(axis));
119
- return legacyValue;
120
- }
121
- return null;
111
+ closeMobile() {
112
+ this.mobileOpen.set(false);
113
+ }
114
+ toggleMobile() {
115
+ this.mobileOpen.update((v) => !v);
122
116
  }
123
- key(axis) {
124
- return this.config.keys[axis];
117
+ isGroupOpen(id) {
118
+ return this.openGroups().has(id);
125
119
  }
126
- legacyKey(axis) {
127
- return `${this.config.legacyPrefix}:${axis}`;
120
+ toggleGroup(id) {
121
+ const next = new Set(this.openGroups());
122
+ if (next.has(id))
123
+ next.delete(id);
124
+ else
125
+ next.add(id);
126
+ this.openGroups.set(next);
128
127
  }
128
+ setGroupOpen(id, open) {
129
+ const next = new Set(this.openGroups());
130
+ if (open)
131
+ next.add(id);
132
+ else
133
+ next.delete(id);
134
+ this.openGroups.set(next);
135
+ }
136
+ /** Apakah id termasuk dalam active trail saat ini. */
137
+ isActive(id) {
138
+ return !!id && this.activeTrail().has(id);
139
+ }
140
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
141
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NavigationService, providedIn: 'root' });
129
142
  }
130
- const STORAGE_KEYS = {
131
- 'horizontal-variant': 'navigation-horizontal-variant',
132
- 'vertical-appearance': 'navigation-vertical-appearance',
133
- 'active-item': 'navigation-active-item',
134
- 'expanded-items': 'navigation-expanded-items',
135
- };
136
- const LEGACY_STORAGE_PREFIX = 'ng-navigation:v1';
137
- function isHorizontalNavigationVariant(value) {
138
- return value === 'default' || value === 'mega';
143
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NavigationService, decorators: [{
144
+ type: Injectable,
145
+ args: [{ providedIn: 'root' }]
146
+ }], ctorParameters: () => [] });
147
+
148
+ const MATERIAL_SYMBOLS_FONT_ATTR = 'data-ui-nav-icon-font';
149
+ const MATERIAL_SYMBOLS_FONT_ID = 'material-symbols-outlined';
150
+ const MATERIAL_SYMBOLS_FONT_HREF = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0';
151
+ /**
152
+ * Material Symbols icon renderer.
153
+ * Menyuntikkan stylesheet font sekali saat dipakai agar consumer tidak perlu
154
+ * menambahkan `<link>` manual di `index.html`.
155
+ */
156
+ class UiNavIconComponent {
157
+ doc = inject(DOCUMENT);
158
+ platformId = inject(PLATFORM_ID);
159
+ name = input('', ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
160
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
161
+ size = input(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
162
+ fontVariationSettings = '"FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24';
163
+ constructor() {
164
+ this.ensureFontStylesheet();
165
+ }
166
+ classes = computed(() => {
167
+ const base = 'material-symbols-outlined inline-flex items-center justify-center leading-none select-none';
168
+ const extra = this.class();
169
+ return extra ? `${base} ${extra}` : base;
170
+ }, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
171
+ ensureFontStylesheet() {
172
+ if (!isPlatformBrowser(this.platformId) || !this.doc.head)
173
+ return;
174
+ const existing = this.doc.head.querySelector(`link[${MATERIAL_SYMBOLS_FONT_ATTR}="${MATERIAL_SYMBOLS_FONT_ID}"]`);
175
+ if (existing)
176
+ return;
177
+ const link = this.doc.createElement('link');
178
+ link.rel = 'stylesheet';
179
+ link.href = MATERIAL_SYMBOLS_FONT_HREF;
180
+ link.setAttribute(MATERIAL_SYMBOLS_FONT_ATTR, MATERIAL_SYMBOLS_FONT_ID);
181
+ this.doc.head.appendChild(link);
182
+ }
183
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UiNavIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
184
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: UiNavIconComponent, isStandalone: true, selector: "ui-nav-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true", "translate": "no" }, properties: { "class": "classes()", "style.font-size.px": "size()", "style.font-variation-settings": "fontVariationSettings" } }, ngImport: i0, template: `{{ name() }}`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
139
185
  }
140
- function isVerticalNavigationAppearance(value) {
141
- return value === 'default' || value === 'collapsible';
186
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UiNavIconComponent, decorators: [{
187
+ type: Component,
188
+ args: [{
189
+ selector: 'ui-nav-icon',
190
+ changeDetection: ChangeDetectionStrategy.OnPush,
191
+ host: {
192
+ '[class]': 'classes()',
193
+ '[style.font-size.px]': 'size()',
194
+ '[style.font-variation-settings]': 'fontVariationSettings',
195
+ 'aria-hidden': 'true',
196
+ translate: 'no',
197
+ },
198
+ template: `{{ name() }}`,
199
+ }]
200
+ }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
201
+
202
+ /** Concatenate and dedupe Tailwind class names. */
203
+ function cn(...inputs) {
204
+ return twMerge(clsx(inputs));
142
205
  }
143
- function normalizeVerticalNavigationAppearance(value) {
144
- if (value === null) {
145
- return null;
206
+
207
+ /**
208
+ * Recursive navigation item renderer (vertical context).
209
+ *
210
+ * Menerima item polymorphic dan delegasi ke template sesuai `type`.
211
+ * Mega dirender sebagai group biasa saat muncul di konteks vertical.
212
+ */
213
+ class UiNavItemComponent {
214
+ nav = inject(NavigationService);
215
+ cn = cn;
216
+ item = input.required(...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
217
+ level = input(0, ...(ngDevMode ? [{ debugName: "level" }] : /* istanbul ignore next */ []));
218
+ /** Compact / icon-only rendering (sidebar `thin`). */
219
+ compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
220
+ exactMatch = {
221
+ exact: true,
222
+ paths: 'exact',
223
+ queryParams: 'exact',
224
+ fragment: 'exact',
225
+ matrixParams: 'exact',
226
+ };
227
+ inexactMatch = {
228
+ paths: 'subset',
229
+ queryParams: 'subset',
230
+ fragment: 'ignored',
231
+ matrixParams: 'ignored',
232
+ };
233
+ type = computed(() => this.item().type, ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
234
+ groupItem = computed(() => this.item(), ...(ngDevMode ? [{ debugName: "groupItem" }] : /* istanbul ignore next */ []));
235
+ collapsableItem = computed(() => this.item(), ...(ngDevMode ? [{ debugName: "collapsableItem" }] : /* istanbul ignore next */ []));
236
+ megaItem = computed(() => this.item(), ...(ngDevMode ? [{ debugName: "megaItem" }] : /* istanbul ignore next */ []));
237
+ asideItem = computed(() => this.item(), ...(ngDevMode ? [{ debugName: "asideItem" }] : /* istanbul ignore next */ []));
238
+ basicItem = computed(() => this.item(), ...(ngDevMode ? [{ debugName: "basicItem" }] : /* istanbul ignore next */ []));
239
+ headingId = computed(() => {
240
+ const id = this.item().id ?? '';
241
+ return `nav-group-${id}`;
242
+ }, ...(ngDevMode ? [{ debugName: "headingId" }] : /* istanbul ignore next */ []));
243
+ isGroupOpen() {
244
+ const id = this.item().id;
245
+ if (!id)
246
+ return false;
247
+ // auto-open when any descendant is active
248
+ if (this.nav.isActive(id) && 'children' in this.item())
249
+ return true;
250
+ return this.nav.isGroupOpen(id);
146
251
  }
147
- if (isVerticalNavigationAppearance(value)) {
148
- return value;
252
+ isTrailActive() {
253
+ return this.nav.isActive(this.item().id);
149
254
  }
150
- if (value === 'compact' || value === 'dense' || value === 'thin') {
151
- return 'collapsible';
255
+ toggleGroup() {
256
+ const id = this.item().id;
257
+ if (id)
258
+ this.nav.toggleGroup(id);
152
259
  }
153
- return null;
154
- }
155
- function normalizeExpandedItemIds(ids) {
156
- return Array.from(new Set(ids.map((id) => id.trim()).filter((id) => id.length > 0)));
157
- }
158
- function parseExpandedItemIds(value) {
159
- if (value === null) {
160
- return [];
161
- }
162
- try {
163
- const parsed = JSON.parse(value);
164
- if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== 'string')) {
165
- return null;
260
+ runAction() {
261
+ const item = this.item();
262
+ if ('action' in item && typeof item.action === 'function') {
263
+ item.action(item);
166
264
  }
167
- return normalizeExpandedItemIds(parsed);
168
265
  }
169
- catch {
170
- return null;
266
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UiNavItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
267
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: UiNavItemComponent, isStandalone: true, selector: "ui-nav-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
268
+ @switch (type()) {
269
+ @case ('divider') {
270
+ <hr class="my-2 border-t border-border" role="separator" />
271
+ }
272
+ @case ('spacer') {
273
+ <div class="flex-1"></div>
274
+ }
275
+ @case ('group') {
276
+ <div class="p-3" role="group" [attr.aria-labelledby]="headingId()">
277
+ @if (!compact()) {
278
+ <div class="sticky top-0 z-10 bg-background py-3 text-muted-foreground">
279
+ <div [id]="headingId()" [class]="cn('ui-nav-heading text-muted-foreground', item().classes?.title)">
280
+ {{ item().title }}
281
+ </div>
282
+ </div>
283
+ }
284
+ <div class="flex flex-col gap-0.5">
285
+ @for (child of groupItem().children; track child.id) {
286
+ <ui-nav-item [item]="child" [level]="level() + 1" [compact]="compact()" />
287
+ }
288
+ </div>
289
+ </div>
290
+ }
291
+ @case ('collapsable') {
292
+ @let id = collapsableItem().id ?? '';
293
+ @let open = isGroupOpen();
294
+ <button
295
+ type="button"
296
+ [class]="
297
+ cn(
298
+ 'ui-nav-text group/ni flex w-full items-center gap-3 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
299
+ isTrailActive() && 'text-primary',
300
+ item().classes?.wrapper
301
+ )
302
+ "
303
+ [attr.aria-expanded]="open"
304
+ [attr.aria-controls]="id + '-panel'"
305
+ [disabled]="collapsableItem().disabled || null"
306
+ [matTooltip]="compact() ? (collapsableItem().title ?? '') : ''"
307
+ matTooltipPosition="right"
308
+ [matTooltipDisabled]="!compact()"
309
+ (click)="toggleGroup()">
310
+ @if (collapsableItem().icon) {
311
+ <ui-nav-icon [name]="collapsableItem().icon!" [size]="18" [class]="item().classes?.icon ?? ''" />
312
+ }
313
+ @if (!compact()) {
314
+ <span [class]="cn('flex-1 truncate text-left', item().classes?.title)">
315
+ {{ collapsableItem().title }}
316
+ </span>
317
+ @if (collapsableItem().badge; as badge) {
318
+ <span [class]="badge.classes ?? 'ui-nav-badge ml-auto'">{{ badge.title }}</span>
319
+ }
320
+ <ui-nav-icon
321
+ [name]="'chevron_right'"
322
+ [size]="18"
323
+ [class]="cn('transition-transform duration-200', open && 'rotate-90')" />
324
+ }
325
+ </button>
326
+ @if (!compact() && open) {
327
+ <div [id]="id + '-panel'" role="region" class="ml-3 mt-0.5 flex flex-col gap-0.5 border-l border-border pl-3">
328
+ @for (child of collapsableItem().children; track child.id) {
329
+ <ui-nav-item [item]="child" [level]="level() + 1" [compact]="false" />
330
+ }
331
+ </div>
332
+ }
333
+ }
334
+ @case ('mega') {
335
+ <!-- Mega direndahkan ke group saat berada di sidebar vertical. -->
336
+ <div class="mt-4 py-3 first:mt-0" role="group">
337
+ @if (!compact()) {
338
+ <div class="ui-nav-heading sticky top-0 z-10 bg-background px-3 pb-1 text-muted-foreground">
339
+ {{ item().title }}
340
+ </div>
341
+ }
342
+ <div class="flex flex-col gap-0.5">
343
+ @for (child of megaItem().children; track child.id) {
344
+ <ui-nav-item [item]="child" [level]="level() + 1" [compact]="compact()" />
345
+ }
346
+ </div>
347
+ </div>
348
+ }
349
+ @case ('aside') {
350
+ <a
351
+ [class]="
352
+ cn(
353
+ 'ui-nav-text flex items-center gap-3 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-primary',
354
+ item().classes?.wrapper
355
+ )
356
+ "
357
+ routerLinkActive="text-primary"
358
+ #rla="routerLinkActive"
359
+ [attr.aria-current]="rla.isActive ? 'page' : null"
360
+ [routerLink]="asideItem().link"
361
+ [queryParams]="asideItem().queryParams"
362
+ [fragment]="asideItem().fragment ?? undefined"
363
+ [target]="asideItem().target ?? undefined"
364
+ [matTooltip]="compact() ? (asideItem().title ?? '') : ''"
365
+ matTooltipPosition="right"
366
+ [matTooltipDisabled]="!compact()">
367
+ @if (asideItem().icon) {
368
+ <ui-nav-icon [name]="asideItem().icon!" [size]="18" />
369
+ }
370
+ @if (!compact()) {
371
+ <span class="flex-1 truncate">{{ asideItem().title }}</span>
372
+ }
373
+ </a>
374
+ }
375
+ @default {
376
+ <!-- basic -->
377
+ @if (basicItem().link && !basicItem().externalLink) {
378
+ <a
379
+ [class]="
380
+ cn(
381
+ 'ui-nav-text flex items-center gap-3 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-primary aria-disabled:pointer-events-none aria-disabled:opacity-50',
382
+ item().classes?.wrapper
383
+ )
384
+ "
385
+ routerLinkActive="text-primary"
386
+ #rla="routerLinkActive"
387
+ [routerLinkActiveOptions]="
388
+ basicItem().isActiveMatchOptions ?? (basicItem().exactMatch ? exactMatch : inexactMatch)
389
+ "
390
+ [attr.aria-current]="rla.isActive ? 'page' : null"
391
+ [attr.aria-disabled]="basicItem().disabled || null"
392
+ [matTooltip]="compact() ? (basicItem().title ?? '') : ''"
393
+ matTooltipPosition="right"
394
+ [matTooltipDisabled]="!compact()"
395
+ [routerLink]="basicItem().link"
396
+ [queryParams]="basicItem().queryParams"
397
+ [queryParamsHandling]="basicItem().queryParamsHandling ?? null"
398
+ [fragment]="basicItem().fragment ?? undefined"
399
+ [preserveFragment]="basicItem().preserveFragment ?? false"
400
+ [target]="basicItem().target ?? undefined"
401
+ (click)="runAction()">
402
+ @if (basicItem().icon) {
403
+ <ui-nav-icon [name]="basicItem().icon!" [size]="18" [class]="item().classes?.icon ?? ''" />
404
+ }
405
+ @if (!compact()) {
406
+ <span [class]="cn('flex-1 truncate', item().classes?.title)">{{ basicItem().title }}</span>
407
+ @if (basicItem().badge; as badge) {
408
+ <span [class]="badge.classes ?? 'ui-nav-badge ml-auto'">{{ badge.title }}</span>
409
+ }
410
+ }
411
+ </a>
412
+ } @else if (basicItem().link && basicItem().externalLink) {
413
+ <a
414
+ [class]="
415
+ cn(
416
+ 'ui-nav-text flex items-center gap-3 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
417
+ item().classes?.wrapper
418
+ )
419
+ "
420
+ [attr.href]="basicItem().link"
421
+ [attr.target]="basicItem().target ?? '_blank'"
422
+ rel="noopener noreferrer"
423
+ [matTooltip]="compact() ? (basicItem().title ?? '') : ''"
424
+ matTooltipPosition="right"
425
+ [matTooltipDisabled]="!compact()">
426
+ @if (basicItem().icon) {
427
+ <ui-nav-icon [name]="basicItem().icon!" [size]="18" />
428
+ }
429
+ @if (!compact()) {
430
+ <span class="flex-1 truncate">{{ basicItem().title }}</span>
431
+ }
432
+ </a>
433
+ } @else {
434
+ <button
435
+ type="button"
436
+ [class]="
437
+ cn(
438
+ 'ui-nav-text flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
439
+ item().classes?.wrapper
440
+ )
441
+ "
442
+ [disabled]="basicItem().disabled || null"
443
+ [matTooltip]="compact() ? (basicItem().title ?? '') : ''"
444
+ matTooltipPosition="right"
445
+ [matTooltipDisabled]="!compact()"
446
+ (click)="runAction()">
447
+ @if (basicItem().icon) {
448
+ <ui-nav-icon [name]="basicItem().icon!" [size]="18" />
449
+ }
450
+ @if (!compact()) {
451
+ <span class="flex-1 truncate">{{ basicItem().title }}</span>
452
+ }
453
+ </button>
454
+ }
455
+ }
171
456
  }
457
+ `, isInline: true, dependencies: [{ kind: "component", type: UiNavItemComponent, selector: "ui-nav-item", inputs: ["item", "level", "compact"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: UiNavIconComponent, selector: "ui-nav-icon", inputs: ["name", "class", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
172
458
  }
173
- function collectNavigationMetadata(navigation) {
174
- const allIds = new Set();
175
- const expandableIds = new Set();
176
- const visit = (items) => {
177
- for (const item of items) {
178
- if (item.id) {
179
- allIds.add(item.id);
180
- if (hasNavigationChildren(item)) {
181
- expandableIds.add(item.id);
182
- }
459
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UiNavItemComponent, decorators: [{
460
+ type: Component,
461
+ args: [{
462
+ selector: 'ui-nav-item',
463
+ changeDetection: ChangeDetectionStrategy.OnPush,
464
+ imports: [RouterLink, RouterLinkActive, MatTooltip, UiNavIconComponent],
465
+ template: `
466
+ @switch (type()) {
467
+ @case ('divider') {
468
+ <hr class="my-2 border-t border-border" role="separator" />
469
+ }
470
+ @case ('spacer') {
471
+ <div class="flex-1"></div>
472
+ }
473
+ @case ('group') {
474
+ <div class="p-3" role="group" [attr.aria-labelledby]="headingId()">
475
+ @if (!compact()) {
476
+ <div class="sticky top-0 z-10 bg-background py-3 text-muted-foreground">
477
+ <div [id]="headingId()" [class]="cn('ui-nav-heading text-muted-foreground', item().classes?.title)">
478
+ {{ item().title }}
479
+ </div>
480
+ </div>
481
+ }
482
+ <div class="flex flex-col gap-0.5">
483
+ @for (child of groupItem().children; track child.id) {
484
+ <ui-nav-item [item]="child" [level]="level() + 1" [compact]="compact()" />
485
+ }
486
+ </div>
487
+ </div>
488
+ }
489
+ @case ('collapsable') {
490
+ @let id = collapsableItem().id ?? '';
491
+ @let open = isGroupOpen();
492
+ <button
493
+ type="button"
494
+ [class]="
495
+ cn(
496
+ 'ui-nav-text group/ni flex w-full items-center gap-3 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
497
+ isTrailActive() && 'text-primary',
498
+ item().classes?.wrapper
499
+ )
500
+ "
501
+ [attr.aria-expanded]="open"
502
+ [attr.aria-controls]="id + '-panel'"
503
+ [disabled]="collapsableItem().disabled || null"
504
+ [matTooltip]="compact() ? (collapsableItem().title ?? '') : ''"
505
+ matTooltipPosition="right"
506
+ [matTooltipDisabled]="!compact()"
507
+ (click)="toggleGroup()">
508
+ @if (collapsableItem().icon) {
509
+ <ui-nav-icon [name]="collapsableItem().icon!" [size]="18" [class]="item().classes?.icon ?? ''" />
510
+ }
511
+ @if (!compact()) {
512
+ <span [class]="cn('flex-1 truncate text-left', item().classes?.title)">
513
+ {{ collapsableItem().title }}
514
+ </span>
515
+ @if (collapsableItem().badge; as badge) {
516
+ <span [class]="badge.classes ?? 'ui-nav-badge ml-auto'">{{ badge.title }}</span>
183
517
  }
184
- if (Array.isArray(item.children) && item.children.length > 0) {
185
- visit(item.children);
518
+ <ui-nav-icon
519
+ [name]="'chevron_right'"
520
+ [size]="18"
521
+ [class]="cn('transition-transform duration-200', open && 'rotate-90')" />
522
+ }
523
+ </button>
524
+ @if (!compact() && open) {
525
+ <div [id]="id + '-panel'" role="region" class="ml-3 mt-0.5 flex flex-col gap-0.5 border-l border-border pl-3">
526
+ @for (child of collapsableItem().children; track child.id) {
527
+ <ui-nav-item [item]="child" [level]="level() + 1" [compact]="false" />
186
528
  }
529
+ </div>
187
530
  }
188
- };
189
- visit(navigation);
190
- return { allIds, expandableIds };
191
- }
192
- class NavigationPreferencesService {
193
- config = inject(NG_NAVIGATION_CONFIG);
194
- document = inject(DOCUMENT);
195
- isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
196
- storage = new NavigationStorageAdapter({
197
- isBrowser: this.isBrowser,
198
- storage: this.document.defaultView?.localStorage ?? null,
199
- keys: STORAGE_KEYS,
200
- legacyPrefix: LEGACY_STORAGE_PREFIX,
201
- });
202
- _horizontalVariant = signal(this.readHorizontalVariant(), ...(ngDevMode ? [{ debugName: "_horizontalVariant" }] : /* istanbul ignore next */ []));
203
- _verticalAppearance = signal(this.readVerticalAppearance(), ...(ngDevMode ? [{ debugName: "_verticalAppearance" }] : /* istanbul ignore next */ []));
204
- _activeItemId = signal(this.readActiveItemId(), ...(ngDevMode ? [{ debugName: "_activeItemId" }] : /* istanbul ignore next */ []));
205
- _expandedItemIds = signal(this.readExpandedItemIds(), ...(ngDevMode ? [{ debugName: "_expandedItemIds" }] : /* istanbul ignore next */ []));
206
- horizontalVariant = this._horizontalVariant.asReadonly();
207
- verticalAppearance = this._verticalAppearance.asReadonly();
208
- activeItemId = this._activeItemId.asReadonly();
209
- expandedItemIds = this._expandedItemIds.asReadonly();
210
- setHorizontalVariant(value) {
211
- this.storage.persist('horizontal-variant', value);
212
- this._horizontalVariant.set(value);
213
- }
214
- setVerticalAppearance(value) {
215
- this.storage.persist('vertical-appearance', value);
216
- this._verticalAppearance.set(value);
217
- }
218
- setActiveItem(id) {
219
- const normalizedId = id?.trim() ?? null;
220
- if (!normalizedId) {
221
- this.clearActiveItem();
222
- return;
531
+ }
532
+ @case ('mega') {
533
+ <!-- Mega direndahkan ke group saat berada di sidebar vertical. -->
534
+ <div class="mt-4 py-3 first:mt-0" role="group">
535
+ @if (!compact()) {
536
+ <div class="ui-nav-heading sticky top-0 z-10 bg-background px-3 pb-1 text-muted-foreground">
537
+ {{ item().title }}
538
+ </div>
539
+ }
540
+ <div class="flex flex-col gap-0.5">
541
+ @for (child of megaItem().children; track child.id) {
542
+ <ui-nav-item [item]="child" [level]="level() + 1" [compact]="compact()" />
543
+ }
544
+ </div>
545
+ </div>
546
+ }
547
+ @case ('aside') {
548
+ <a
549
+ [class]="
550
+ cn(
551
+ 'ui-nav-text flex items-center gap-3 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-primary',
552
+ item().classes?.wrapper
553
+ )
554
+ "
555
+ routerLinkActive="text-primary"
556
+ #rla="routerLinkActive"
557
+ [attr.aria-current]="rla.isActive ? 'page' : null"
558
+ [routerLink]="asideItem().link"
559
+ [queryParams]="asideItem().queryParams"
560
+ [fragment]="asideItem().fragment ?? undefined"
561
+ [target]="asideItem().target ?? undefined"
562
+ [matTooltip]="compact() ? (asideItem().title ?? '') : ''"
563
+ matTooltipPosition="right"
564
+ [matTooltipDisabled]="!compact()">
565
+ @if (asideItem().icon) {
566
+ <ui-nav-icon [name]="asideItem().icon!" [size]="18" />
567
+ }
568
+ @if (!compact()) {
569
+ <span class="flex-1 truncate">{{ asideItem().title }}</span>
570
+ }
571
+ </a>
572
+ }
573
+ @default {
574
+ <!-- basic -->
575
+ @if (basicItem().link && !basicItem().externalLink) {
576
+ <a
577
+ [class]="
578
+ cn(
579
+ 'ui-nav-text flex items-center gap-3 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-primary aria-disabled:pointer-events-none aria-disabled:opacity-50',
580
+ item().classes?.wrapper
581
+ )
582
+ "
583
+ routerLinkActive="text-primary"
584
+ #rla="routerLinkActive"
585
+ [routerLinkActiveOptions]="
586
+ basicItem().isActiveMatchOptions ?? (basicItem().exactMatch ? exactMatch : inexactMatch)
587
+ "
588
+ [attr.aria-current]="rla.isActive ? 'page' : null"
589
+ [attr.aria-disabled]="basicItem().disabled || null"
590
+ [matTooltip]="compact() ? (basicItem().title ?? '') : ''"
591
+ matTooltipPosition="right"
592
+ [matTooltipDisabled]="!compact()"
593
+ [routerLink]="basicItem().link"
594
+ [queryParams]="basicItem().queryParams"
595
+ [queryParamsHandling]="basicItem().queryParamsHandling ?? null"
596
+ [fragment]="basicItem().fragment ?? undefined"
597
+ [preserveFragment]="basicItem().preserveFragment ?? false"
598
+ [target]="basicItem().target ?? undefined"
599
+ (click)="runAction()">
600
+ @if (basicItem().icon) {
601
+ <ui-nav-icon [name]="basicItem().icon!" [size]="18" [class]="item().classes?.icon ?? ''" />
602
+ }
603
+ @if (!compact()) {
604
+ <span [class]="cn('flex-1 truncate', item().classes?.title)">{{ basicItem().title }}</span>
605
+ @if (basicItem().badge; as badge) {
606
+ <span [class]="badge.classes ?? 'ui-nav-badge ml-auto'">{{ badge.title }}</span>
607
+ }
608
+ }
609
+ </a>
610
+ } @else if (basicItem().link && basicItem().externalLink) {
611
+ <a
612
+ [class]="
613
+ cn(
614
+ 'ui-nav-text flex items-center gap-3 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
615
+ item().classes?.wrapper
616
+ )
617
+ "
618
+ [attr.href]="basicItem().link"
619
+ [attr.target]="basicItem().target ?? '_blank'"
620
+ rel="noopener noreferrer"
621
+ [matTooltip]="compact() ? (basicItem().title ?? '') : ''"
622
+ matTooltipPosition="right"
623
+ [matTooltipDisabled]="!compact()">
624
+ @if (basicItem().icon) {
625
+ <ui-nav-icon [name]="basicItem().icon!" [size]="18" />
626
+ }
627
+ @if (!compact()) {
628
+ <span class="flex-1 truncate">{{ basicItem().title }}</span>
629
+ }
630
+ </a>
631
+ } @else {
632
+ <button
633
+ type="button"
634
+ [class]="
635
+ cn(
636
+ 'ui-nav-text flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
637
+ item().classes?.wrapper
638
+ )
639
+ "
640
+ [disabled]="basicItem().disabled || null"
641
+ [matTooltip]="compact() ? (basicItem().title ?? '') : ''"
642
+ matTooltipPosition="right"
643
+ [matTooltipDisabled]="!compact()"
644
+ (click)="runAction()">
645
+ @if (basicItem().icon) {
646
+ <ui-nav-icon [name]="basicItem().icon!" [size]="18" />
647
+ }
648
+ @if (!compact()) {
649
+ <span class="flex-1 truncate">{{ basicItem().title }}</span>
650
+ }
651
+ </button>
223
652
  }
224
- this.storage.persist('active-item', normalizedId);
225
- this._activeItemId.set(normalizedId);
653
+ }
226
654
  }
227
- clearActiveItem() {
228
- this.storage.clear('active-item');
229
- this._activeItemId.set(null);
655
+ `,
656
+ }]
657
+ }], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }] } });
658
+
659
+ /**
660
+ * Vertical navigation (sidebar) — shadcn-styled.
661
+ *
662
+ * Variants:
663
+ * - `default`: 17.5rem, label + icon
664
+ * - `thin`: 4rem icon-only; hover memunculkan overlay expand (tidak push konten)
665
+ *
666
+ * Mobile (`< md`): saat `autoMobile=true` (default), host desktop disembunyikan
667
+ * dan konten dirender lewat CDK Overlay drawer dengan focus trap. State buka
668
+ * dikontrol lewat `NavigationService.mobileOpen`.
669
+ */
670
+ class SidebarComponent {
671
+ nav = inject(NavigationService);
672
+ bp = inject(BreakpointObserver);
673
+ overlay = inject(Overlay);
674
+ vcr = inject(ViewContainerRef);
675
+ focusTrapFactory = inject(FocusTrapFactory);
676
+ doc = inject(DOCUMENT);
677
+ destroyRef = inject(DestroyRef);
678
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
679
+ /** Registry key di `NavigationService`. Default `'main'`. */
680
+ navigationId = input(DEFAULT_NAVIGATION_ID, ...(ngDevMode ? [{ debugName: "navigationId" }] : /* istanbul ignore next */ []));
681
+ appearance = input('default', ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
682
+ position = input('left', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
683
+ ariaLabel = input('Primary', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
684
+ header = input(true, ...(ngDevMode ? [{ debugName: "header" }] : /* istanbul ignore next */ []));
685
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
686
+ /** Auto switch ke CDK overlay drawer saat viewport `< md`. */
687
+ autoMobile = input(true, ...(ngDevMode ? [{ debugName: "autoMobile" }] : /* istanbul ignore next */ []));
688
+ /** Auto-register `items` ke `NavigationService` agar `activeTrail` bekerja. */
689
+ autoRegister = input(true, ...(ngDevMode ? [{ debugName: "autoRegister" }] : /* istanbul ignore next */ []));
690
+ /** Resolved items: input jika disediakan, fallback ke registry NavigationService. */
691
+ resolvedItems = computed(() => {
692
+ const explicit = this.items();
693
+ return explicit.length > 0 ? explicit : this.nav.getItems(this.navigationId())();
694
+ }, ...(ngDevMode ? [{ debugName: "resolvedItems" }] : /* istanbul ignore next */ []));
695
+ hovered = signal(false, ...(ngDevMode ? [{ debugName: "hovered" }] : /* istanbul ignore next */ []));
696
+ drawerTpl = viewChild.required('drawerTpl');
697
+ drawerRef = null;
698
+ focusTrap = null;
699
+ previouslyFocused = null;
700
+ /** True saat viewport `< md` (767.98px). */
701
+ isMobileMedia = toSignal(this.bp.observe('(max-width: 767.98px)').pipe(map((s) => s.matches)), {
702
+ initialValue: false,
703
+ });
704
+ isMobile = computed(() => this.autoMobile() && this.isMobileMedia(), ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
705
+ isExpanded = computed(() => this.appearance() === 'default' || this.hovered(), ...(ngDevMode ? [{ debugName: "isExpanded" }] : /* istanbul ignore next */ []));
706
+ isCompact = computed(() => !this.isMobile() && this.appearance() === 'thin' && !this.hovered(), ...(ngDevMode ? [{ debugName: "isCompact" }] : /* istanbul ignore next */ []));
707
+ constructor() {
708
+ // Auto-register items ke service untuk active trail (hanya jika input non-kosong).
709
+ effect(() => {
710
+ const explicit = this.items();
711
+ if (this.autoRegister() && explicit.length > 0)
712
+ this.nav.registerItems(this.navigationId(), explicit);
713
+ });
714
+ // Kelola overlay drawer berdasarkan mobileOpen + isMobile.
715
+ effect(() => {
716
+ const open = this.nav.mobileOpen();
717
+ const mobile = this.isMobile();
718
+ if (mobile && open)
719
+ this.openDrawer();
720
+ else
721
+ this.closeDrawer();
722
+ });
723
+ this.destroyRef.onDestroy(() => this.closeDrawer());
230
724
  }
231
- setExpandedItemIds(ids) {
232
- const normalizedIds = normalizeExpandedItemIds(ids);
233
- if (normalizedIds.length === 0) {
234
- this.clearExpandedItems();
235
- return;
725
+ hostClasses = computed(() => {
726
+ const base = ['relative flex shrink-0 bg-background text-foreground', 'transition-[width] duration-200 ease-out'];
727
+ const appearance = this.appearance();
728
+ if (appearance === 'thin')
729
+ base.push('w-16');
730
+ else
731
+ base.push('[width:17.5rem]');
732
+ if (this.position() === 'right')
733
+ base.push('border-l');
734
+ else
735
+ base.push('border-r');
736
+ base.push('border-border');
737
+ return [...base, this.class()].join(' ');
738
+ }, ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
739
+ innerClasses = computed(() => {
740
+ const overlayActive = this.appearance() === 'thin' && this.hovered();
741
+ const base = ['flex h-full flex-col transition-[width] duration-200 ease-out'];
742
+ if (overlayActive) {
743
+ base.push('absolute inset-y-0 z-30 bg-background shadow-xl [width:17.5rem]', this.position() === 'right' ? 'right-0 border-l border-border' : 'left-0 border-r border-border');
236
744
  }
237
- this.storage.persist('expanded-items', JSON.stringify(normalizedIds));
238
- this._expandedItemIds.set(normalizedIds);
239
- }
240
- expandItem(id) {
241
- const normalizedId = id.trim();
242
- if (!normalizedId || this._expandedItemIds().includes(normalizedId)) {
243
- return;
745
+ else {
746
+ base.push('w-full');
244
747
  }
245
- this.setExpandedItemIds([...this._expandedItemIds(), normalizedId]);
748
+ return base.join(' ');
749
+ }, ...(ngDevMode ? [{ debugName: "innerClasses" }] : /* istanbul ignore next */ []));
750
+ onHoverEnter() {
751
+ if (this.appearance() === 'thin' && !this.isMobile())
752
+ this.hovered.set(true);
246
753
  }
247
- collapseItem(id) {
248
- const normalizedId = id.trim();
249
- if (!normalizedId || !this._expandedItemIds().includes(normalizedId)) {
250
- return;
251
- }
252
- this.setExpandedItemIds(this._expandedItemIds().filter((expandedId) => expandedId !== normalizedId));
754
+ onHoverLeave() {
755
+ if (this.appearance() === 'thin')
756
+ this.hovered.set(false);
253
757
  }
254
- toggleExpandedItem(id) {
255
- const normalizedId = id.trim();
256
- if (!normalizedId) {
758
+ /** Touch fallback: tap pada strip thin (ketika belum expanded) untuk expand. */
759
+ onHostClick(event) {
760
+ if (this.appearance() !== 'thin' || this.isMobile())
257
761
  return;
258
- }
259
- if (this._expandedItemIds().includes(normalizedId)) {
260
- this.collapseItem(normalizedId);
762
+ if (this.hovered())
261
763
  return;
262
- }
263
- this.expandItem(normalizedId);
264
- }
265
- clearExpandedItems() {
266
- this.storage.clear('expanded-items');
267
- this._expandedItemIds.set([]);
268
- }
269
- reset() {
270
- this.storage.clear('horizontal-variant');
271
- this._horizontalVariant.set(this.config.defaultHorizontalVariant);
272
- this.storage.clear('vertical-appearance');
273
- this._verticalAppearance.set(this.config.defaultVerticalVariant);
274
- this.clearActiveItem();
275
- this.clearExpandedItems();
276
- }
277
- syncWithNavigation(navigation) {
278
- const { allIds, expandableIds } = collectNavigationMetadata(navigation);
279
- const activeItemId = this._activeItemId();
280
- if (activeItemId && !allIds.has(activeItemId)) {
281
- this.clearActiveItem();
282
- }
283
- const filteredExpandedIds = this._expandedItemIds().filter((id) => expandableIds.has(id));
284
- if (filteredExpandedIds.length !== this._expandedItemIds().length) {
285
- this.setExpandedItemIds(filteredExpandedIds);
286
- }
764
+ const target = event.target;
765
+ // Biarkan klik pada control interaktif terus propagate (tidak intercept).
766
+ if (target && target.closest('a,button,[role="menuitem"]'))
767
+ return;
768
+ this.hovered.set(true);
287
769
  }
288
- readActiveItemId() {
289
- const storedValue = this.storage.readValue('active-item')?.trim();
290
- if (!storedValue) {
291
- this.storage.clear('active-item');
292
- return null;
293
- }
294
- return storedValue;
295
- }
296
- readExpandedItemIds() {
297
- const storedValue = this.storage.readValue('expanded-items');
298
- const parsedValue = parseExpandedItemIds(storedValue);
299
- if (parsedValue === null) {
300
- this.storage.clear('expanded-items');
301
- return [];
302
- }
303
- return parsedValue;
770
+ openDrawer() {
771
+ if (this.drawerRef)
772
+ return;
773
+ const side = this.position();
774
+ const pos = this.overlay.position().global().top('0');
775
+ if (side === 'right')
776
+ pos.right('0');
777
+ else
778
+ pos.left('0');
779
+ this.drawerRef = this.overlay.create({
780
+ positionStrategy: pos,
781
+ height: '100vh',
782
+ hasBackdrop: true,
783
+ backdropClass: 'cdk-overlay-dark-backdrop',
784
+ scrollStrategy: this.overlay.scrollStrategies.block(),
785
+ panelClass: ['ui-sidebar-drawer'],
786
+ });
787
+ const portal = new TemplatePortal(this.drawerTpl(), this.vcr);
788
+ const viewRef = this.drawerRef.attach(portal);
789
+ viewRef.detectChanges();
790
+ const root = this.drawerRef.overlayElement;
791
+ this.focusTrap = this.focusTrapFactory.create(root);
792
+ this.previouslyFocused = this.doc.activeElement;
793
+ queueMicrotask(() => this.focusTrap?.focusInitialElementWhenReady());
794
+ this.drawerRef
795
+ .backdropClick()
796
+ .pipe(takeUntilDestroyed(this.destroyRef))
797
+ .subscribe(() => this.nav.closeMobile());
798
+ this.drawerRef
799
+ .keydownEvents()
800
+ .pipe(filter((e) => e.key === 'Escape'), takeUntilDestroyed(this.destroyRef))
801
+ .subscribe(() => this.nav.closeMobile());
304
802
  }
305
- readHorizontalVariant() {
306
- const storedValue = this.storage.readValue('horizontal-variant');
307
- if (storedValue && isHorizontalNavigationVariant(storedValue)) {
308
- return storedValue;
309
- }
310
- if (storedValue !== null) {
311
- this.storage.clear('horizontal-variant');
312
- }
313
- return this.config.defaultHorizontalVariant;
314
- }
315
- readVerticalAppearance() {
316
- const storedValue = this.storage.readValue('vertical-appearance');
317
- const normalizedValue = normalizeVerticalNavigationAppearance(storedValue);
318
- if (normalizedValue) {
319
- if (storedValue !== normalizedValue) {
320
- this.storage.persist('vertical-appearance', normalizedValue);
321
- }
322
- return normalizedValue;
323
- }
324
- if (storedValue !== null) {
325
- this.storage.clear('vertical-appearance');
326
- }
327
- return this.config.defaultVerticalVariant;
803
+ closeDrawer() {
804
+ if (!this.drawerRef)
805
+ return;
806
+ this.focusTrap?.destroy();
807
+ this.focusTrap = null;
808
+ this.drawerRef.dispose();
809
+ this.drawerRef = null;
810
+ this.previouslyFocused?.focus?.();
811
+ this.previouslyFocused = null;
328
812
  }
329
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NavigationPreferencesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
330
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NavigationPreferencesService, providedIn: 'root' });
813
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
814
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SidebarComponent, isStandalone: true, selector: "ui-sidebar", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, navigationId: { classPropertyName: "navigationId", publicName: "navigationId", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, autoMobile: { classPropertyName: "autoMobile", publicName: "autoMobile", isSignal: true, isRequired: false, transformFunction: null }, autoRegister: { classPropertyName: "autoRegister", publicName: "autoRegister", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation" }, listeners: { "mouseenter": "onHoverEnter()", "mouseleave": "onHoverLeave()", "focusin": "onHoverEnter()", "click": "onHostClick($event)" }, properties: { "attr.aria-label": "ariaLabel()", "class": "hostClasses()", "attr.data-appearance": "appearance()", "attr.data-position": "position()", "attr.data-expanded": "isExpanded()", "hidden": "isMobile()" } }, viewQueries: [{ propertyName: "drawerTpl", first: true, predicate: ["drawerTpl"], descendants: true, isSignal: true }], ngImport: i0, template: `
815
+ <div [class]="innerClasses()">
816
+ <ng-container [ngTemplateOutlet]="body" />
817
+ </div>
818
+
819
+ <ng-template #body>
820
+ @if (header()) {
821
+ <div class="flex h-12 items-center gap-2 border-b border-border px-3">
822
+ <ng-content select="[ui-sidebar-header]" />
823
+ </div>
824
+ }
825
+ <nav class="flex-1 overflow-y-auto overflow-x-hidden">
826
+ @for (item of resolvedItems(); track item.id) {
827
+ <ui-nav-item [item]="item" [compact]="isCompact()" />
828
+ }
829
+ </nav>
830
+ <div class="border-t border-border h-12">
831
+ <ng-content select="[ui-sidebar-footer]" />
832
+ </div>
833
+ </ng-template>
834
+
835
+ <ng-template #drawerTpl>
836
+ <div
837
+ role="dialog"
838
+ aria-modal="true"
839
+ [attr.aria-label]="ariaLabel()"
840
+ class="flex h-full w-72 max-w-[85vw] flex-col bg-background text-foreground shadow-xl"
841
+ [ngClass]="position() === 'right' ? 'border-l border-border' : 'border-r border-border'">
842
+ <ng-container [ngTemplateOutlet]="body" />
843
+ </div>
844
+ </ng-template>
845
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UiNavItemComponent, selector: "ui-nav-item", inputs: ["item", "level", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
331
846
  }
332
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NavigationPreferencesService, decorators: [{
333
- type: Injectable,
334
- args: [{ providedIn: 'root' }]
335
- }] });
847
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SidebarComponent, decorators: [{
848
+ type: Component,
849
+ args: [{
850
+ selector: 'ui-sidebar',
851
+ changeDetection: ChangeDetectionStrategy.OnPush,
852
+ imports: [NgClass, NgTemplateOutlet, UiNavItemComponent],
853
+ host: {
854
+ role: 'navigation',
855
+ '[attr.aria-label]': 'ariaLabel()',
856
+ '[class]': 'hostClasses()',
857
+ '[attr.data-appearance]': 'appearance()',
858
+ '[attr.data-position]': 'position()',
859
+ '[attr.data-expanded]': 'isExpanded()',
860
+ '[hidden]': 'isMobile()',
861
+ '(mouseenter)': 'onHoverEnter()',
862
+ '(mouseleave)': 'onHoverLeave()',
863
+ '(focusin)': 'onHoverEnter()',
864
+ '(click)': 'onHostClick($event)',
865
+ },
866
+ template: `
867
+ <div [class]="innerClasses()">
868
+ <ng-container [ngTemplateOutlet]="body" />
869
+ </div>
870
+
871
+ <ng-template #body>
872
+ @if (header()) {
873
+ <div class="flex h-12 items-center gap-2 border-b border-border px-3">
874
+ <ng-content select="[ui-sidebar-header]" />
875
+ </div>
876
+ }
877
+ <nav class="flex-1 overflow-y-auto overflow-x-hidden">
878
+ @for (item of resolvedItems(); track item.id) {
879
+ <ui-nav-item [item]="item" [compact]="isCompact()" />
880
+ }
881
+ </nav>
882
+ <div class="border-t border-border h-12">
883
+ <ng-content select="[ui-sidebar-footer]" />
884
+ </div>
885
+ </ng-template>
886
+
887
+ <ng-template #drawerTpl>
888
+ <div
889
+ role="dialog"
890
+ aria-modal="true"
891
+ [attr.aria-label]="ariaLabel()"
892
+ class="flex h-full w-72 max-w-[85vw] flex-col bg-background text-foreground shadow-xl"
893
+ [ngClass]="position() === 'right' ? 'border-l border-border' : 'border-r border-border'">
894
+ <ng-container [ngTemplateOutlet]="body" />
895
+ </div>
896
+ </ng-template>
897
+ `,
898
+ }]
899
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], navigationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigationId", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], autoMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoMobile", required: false }] }], autoRegister: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoRegister", required: false }] }], drawerTpl: [{ type: i0.ViewChild, args: ['drawerTpl', { isSignal: true }] }] } });
336
900
 
337
901
  /**
338
- * Navigation Service
902
+ * Horizontal navigation (topbar) — shadcn-styled.
339
903
  *
340
- * Service ini bertanggung jawab untuk:
341
- * 1. Store dan retrieve navigation data
342
- * 2. Menyediakan state aktif dan expanded item
343
- * 3. Utility functions untuk navigation operations
904
+ * Variants:
905
+ * - `default`: horizontal list; item `collapsable` buka dropdown
906
+ * - `megamenu`: item `mega` buka panel full-width multi-kolom
344
907
  */
345
- class NavigationService {
346
- preferences = inject(NavigationPreferencesService);
347
- _navigation = signal([], ...(ngDevMode ? [{ debugName: "_navigation" }] : /* istanbul ignore next */ []));
348
- navigationItems = this._navigation.asReadonly();
349
- activeItemId = this.preferences.activeItemId;
350
- flatNavigation = computed(() => this.getFlatNavigation(this._navigation()), ...(ngDevMode ? [{ debugName: "flatNavigation" }] : /* istanbul ignore next */ []));
351
- expandedItemIds = this.preferences.expandedItemIds;
352
- /**
353
- * Store navigation data
354
- * Biasanya dipanggil saat app initialization
355
- */
356
- storeNavigation(navigation) {
357
- this._navigation.set(navigation);
358
- this.preferences.syncWithNavigation(navigation);
908
+ class TopbarComponent {
909
+ nav = inject(NavigationService);
910
+ overlay = inject(Overlay);
911
+ vcr = inject(ViewContainerRef);
912
+ host = inject(ElementRef);
913
+ destroyRef = inject(DestroyRef);
914
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
915
+ /** Registry key di `NavigationService`. Default `'main'`. */
916
+ navigationId = input(DEFAULT_NAVIGATION_ID, ...(ngDevMode ? [{ debugName: "navigationId" }] : /* istanbul ignore next */ []));
917
+ appearance = input('default', ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
918
+ ariaLabel = input('Primary', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
919
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
920
+ /** Auto-register `items` ke `NavigationService` agar `activeTrail` bekerja. */
921
+ autoRegister = input(true, ...(ngDevMode ? [{ debugName: "autoRegister" }] : /* istanbul ignore next */ []));
922
+ /** Tampilkan hamburger di `< md` yang men-toggle mobile drawer sidebar. */
923
+ showHamburger = input(true, ...(ngDevMode ? [{ debugName: "showHamburger" }] : /* istanbul ignore next */ []));
924
+ hamburgerLabel = input('Open navigation', ...(ngDevMode ? [{ debugName: "hamburgerLabel" }] : /* istanbul ignore next */ []));
925
+ /** Resolved items: input jika disediakan, fallback ke registry NavigationService. */
926
+ resolvedItems = computed(() => {
927
+ const explicit = this.items();
928
+ return explicit.length > 0 ? explicit : this.nav.getItems(this.navigationId())();
929
+ }, ...(ngDevMode ? [{ debugName: "resolvedItems" }] : /* istanbul ignore next */ []));
930
+ openId = signal(null, ...(ngDevMode ? [{ debugName: "openId" }] : /* istanbul ignore next */ []));
931
+ active = null;
932
+ dropdownTpl = viewChild.required('dropdownTpl');
933
+ megaTpl = viewChild.required('megaTpl');
934
+ constructor() {
935
+ effect(() => {
936
+ const explicit = this.items();
937
+ if (this.autoRegister() && explicit.length > 0)
938
+ this.nav.registerItems(this.navigationId(), explicit);
939
+ });
940
+ this.destroyRef.onDestroy(() => this.closeAll());
359
941
  }
360
- /**
361
- * Get navigation data dari storage
362
- */
363
- getNavigation() {
364
- return this._navigation();
942
+ hostClasses = computed(() => {
943
+ return [
944
+ 'sticky top-0 z-20 flex w-full items-center border-b border-border bg-background text-foreground',
945
+ this.class(),
946
+ ].join(' ');
947
+ }, ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
948
+ asBasic(i) {
949
+ return i;
365
950
  }
366
- /**
367
- * Delete navigation data dari storage
368
- */
369
- deleteNavigation() {
370
- if (!this._navigation().length && this.activeItemId() === null && this.expandedItemIds().length === 0) {
371
- return;
372
- }
373
- this._navigation.set([]);
374
- this.clearActiveItem();
375
- this.clearExpandedItems();
951
+ asCollapsable(i) {
952
+ return i;
953
+ }
954
+ asGroup(i) {
955
+ return i;
376
956
  }
377
- setActiveItem(id) {
378
- this.preferences.setActiveItem(id);
957
+ asMega(i) {
958
+ return i;
379
959
  }
380
- clearActiveItem() {
381
- this.preferences.clearActiveItem();
960
+ isItemActive(id) {
961
+ return this.nav.isActive(id);
382
962
  }
383
- getActiveItem(navigation = this._navigation()) {
384
- const activeItemId = this.activeItemId();
385
- if (!activeItemId) {
386
- return null;
963
+ megaColsClass(columns) {
964
+ const c = Math.min(Math.max(columns ?? 4, 1), 6);
965
+ switch (c) {
966
+ case 1:
967
+ return 'grid-cols-1';
968
+ case 2:
969
+ return 'md:grid-cols-2';
970
+ case 3:
971
+ return 'md:grid-cols-3';
972
+ case 5:
973
+ return 'md:grid-cols-5';
974
+ case 6:
975
+ return 'md:grid-cols-6';
976
+ default:
977
+ return 'md:grid-cols-4';
387
978
  }
388
- return this.getItem(activeItemId, navigation);
389
979
  }
390
- expandItem(id) {
391
- this.preferences.expandItem(id);
980
+ toggleDropdown(trigger, item) {
981
+ if (this.openId() === item.id)
982
+ this.closeAll();
983
+ else
984
+ this.openDropdown(trigger, item);
392
985
  }
393
- collapseItem(id) {
394
- this.preferences.collapseItem(id);
986
+ openDropdown(trigger, item) {
987
+ if (this.openId() === item.id)
988
+ return;
989
+ this.closeAll();
990
+ this.attach(trigger, item, this.dropdownTpl(), /*fullWidth*/ false);
395
991
  }
396
- toggleItemExpanded(id) {
397
- this.preferences.toggleExpandedItem(id);
992
+ toggleMega(trigger, item) {
993
+ if (this.openId() === item.id)
994
+ this.closeAll();
995
+ else
996
+ this.openMega(trigger, item);
398
997
  }
399
- isItemExpanded(id) {
400
- return this.expandedItemIds().includes(id);
998
+ openMega(trigger, item) {
999
+ if (this.openId() === item.id)
1000
+ return;
1001
+ this.closeAll();
1002
+ this.attach(trigger, item, this.megaTpl(), /*fullWidth*/ true);
401
1003
  }
402
- clearExpandedItems() {
403
- this.preferences.clearExpandedItems();
1004
+ attach(trigger, item, tpl, fullWidth) {
1005
+ const strategy = this.overlay
1006
+ .position()
1007
+ .flexibleConnectedTo(trigger)
1008
+ .withFlexibleDimensions(false)
1009
+ .withPush(false)
1010
+ .withPositions(fullWidth
1011
+ ? [
1012
+ {
1013
+ originX: 'center',
1014
+ originY: 'bottom',
1015
+ overlayX: 'center',
1016
+ overlayY: 'top',
1017
+ offsetY: 4,
1018
+ },
1019
+ {
1020
+ originX: 'center',
1021
+ originY: 'top',
1022
+ overlayX: 'center',
1023
+ overlayY: 'bottom',
1024
+ offsetY: -4,
1025
+ },
1026
+ ]
1027
+ : [
1028
+ {
1029
+ originX: 'start',
1030
+ originY: 'bottom',
1031
+ overlayX: 'start',
1032
+ overlayY: 'top',
1033
+ offsetY: 4,
1034
+ },
1035
+ {
1036
+ originX: 'start',
1037
+ originY: 'top',
1038
+ overlayX: 'start',
1039
+ overlayY: 'bottom',
1040
+ offsetY: -4,
1041
+ },
1042
+ ]);
1043
+ const ref = this.overlay.create({
1044
+ positionStrategy: strategy,
1045
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
1046
+ hasBackdrop: true,
1047
+ backdropClass: 'cdk-overlay-transparent-backdrop',
1048
+ panelClass: fullWidth ? ['ui-mega-panel'] : ['ui-dropdown-panel'],
1049
+ width: fullWidth ? '100vw' : undefined,
1050
+ maxWidth: fullWidth ? '100vw' : undefined,
1051
+ });
1052
+ const portal = new TemplatePortal(tpl, this.vcr, { $implicit: item });
1053
+ ref.attach(portal);
1054
+ this.active = { ref, id: item.id ?? '' };
1055
+ this.openId.set(item.id ?? null);
1056
+ merge(ref.backdropClick(), ref.keydownEvents().pipe(filter((e) => e.key === 'Escape')))
1057
+ .pipe(takeUntilDestroyed(this.destroyRef))
1058
+ .subscribe(() => {
1059
+ this.closeAll();
1060
+ trigger.focus();
1061
+ });
404
1062
  }
405
- /**
406
- * Flatten navigation array
407
- * Convert nested structure menjadi flat array (hanya basic items)
408
- * Berguna untuk search, analytics, atau operations lainnya
409
- */
410
- getFlatNavigation(navigation, flatNavigation = []) {
411
- for (const item of navigation) {
412
- if (item.type === 'basic') {
413
- flatNavigation.push(item);
414
- }
415
- if (hasNavigationChildren(item)) {
416
- this.getFlatNavigation(getNavigationChildren(item), flatNavigation);
417
- }
1063
+ closeAll() {
1064
+ if (this.active) {
1065
+ this.active.ref.dispose();
1066
+ this.active = null;
418
1067
  }
419
- return flatNavigation;
1068
+ this.openId.set(null);
420
1069
  }
421
- /**
422
- * Get item by ID dari navigation tree
423
- * Recursive search untuk find item dengan id tertentu
424
- */
425
- getItem(id, navigation) {
426
- for (const item of navigation) {
427
- if (item.id === id) {
428
- return item;
429
- }
430
- if (hasNavigationChildren(item)) {
431
- const childItem = this.getItem(id, getNavigationChildren(item));
432
- if (childItem) {
433
- return childItem;
434
- }
435
- }
1070
+ /** Menubar keyboard navigation: ArrowLeft/Right antar trigger, Home/End, ArrowDown fokus panel. */
1071
+ onMenubarKeydown(event) {
1072
+ const key = event.key;
1073
+ const root = this.host.nativeElement;
1074
+ const triggers = Array.from(root.querySelectorAll('ul[role="menubar"] [role="menuitem"]')).filter((el) => !el.hasAttribute('disabled'));
1075
+ if (triggers.length === 0)
1076
+ return;
1077
+ const currentIndex = triggers.indexOf(document.activeElement);
1078
+ if (key === 'ArrowDown' && currentIndex !== -1) {
1079
+ event.preventDefault();
1080
+ // Jika open, fokus item pertama panel; jika belum, focus tetap (panel akan dibuka via click/mouseenter).
1081
+ queueMicrotask(() => this.focusFirstInPanel());
1082
+ return;
1083
+ }
1084
+ if (key !== 'ArrowLeft' && key !== 'ArrowRight' && key !== 'Home' && key !== 'End')
1085
+ return;
1086
+ let nextIndex = currentIndex;
1087
+ if (key === 'ArrowRight')
1088
+ nextIndex = (currentIndex + 1 + triggers.length) % triggers.length;
1089
+ else if (key === 'ArrowLeft')
1090
+ nextIndex = (currentIndex - 1 + triggers.length) % triggers.length;
1091
+ else if (key === 'Home')
1092
+ nextIndex = 0;
1093
+ else if (key === 'End')
1094
+ nextIndex = triggers.length - 1;
1095
+ if (nextIndex !== currentIndex && triggers[nextIndex]) {
1096
+ event.preventDefault();
1097
+ triggers[nextIndex].focus();
436
1098
  }
437
- return null;
438
1099
  }
439
- /**
440
- * Get parent dari item dengan id tertentu
441
- * Berguna untuk breadcrumb atau navigation path
442
- */
443
- getItemParent(id, navigation, parent) {
444
- for (const item of navigation) {
445
- if (item.id === id) {
446
- return parent;
447
- }
448
- if (hasNavigationChildren(item)) {
449
- const result = this.getItemParent(id, getNavigationChildren(item), item);
450
- if (result) {
451
- return result;
452
- }
453
- }
1100
+ /** Arrow-key navigation dalam dropdown/mega panel. */
1101
+ onPanelKeydown(event) {
1102
+ const key = event.key;
1103
+ if (key !== 'ArrowDown' &&
1104
+ key !== 'ArrowUp' &&
1105
+ key !== 'ArrowLeft' &&
1106
+ key !== 'ArrowRight' &&
1107
+ key !== 'Home' &&
1108
+ key !== 'End')
1109
+ return;
1110
+ const panel = this.active?.ref.overlayElement;
1111
+ if (!panel)
1112
+ return;
1113
+ const items = this.collectPanelFocusables(panel);
1114
+ if (items.length === 0)
1115
+ return;
1116
+ const currentIndex = items.indexOf(document.activeElement);
1117
+ let nextIndex = currentIndex;
1118
+ if (key === 'ArrowDown' || key === 'ArrowRight')
1119
+ nextIndex = (currentIndex + 1 + items.length) % items.length;
1120
+ else if (key === 'ArrowUp' || key === 'ArrowLeft')
1121
+ nextIndex = (currentIndex - 1 + items.length) % items.length;
1122
+ else if (key === 'Home')
1123
+ nextIndex = 0;
1124
+ else if (key === 'End')
1125
+ nextIndex = items.length - 1;
1126
+ if (nextIndex !== currentIndex && items[nextIndex]) {
1127
+ event.preventDefault();
1128
+ items[nextIndex].focus();
454
1129
  }
455
- return null;
456
1130
  }
457
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
458
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NavigationService, providedIn: 'root' });
1131
+ collectPanelFocusables(root) {
1132
+ return Array.from(root.querySelectorAll('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((el) => el.offsetParent !== null || el.getClientRects().length > 0);
1133
+ }
1134
+ focusFirstInPanel() {
1135
+ const panel = this.active?.ref.overlayElement;
1136
+ if (!panel)
1137
+ return;
1138
+ const items = this.collectPanelFocusables(panel);
1139
+ items[0]?.focus();
1140
+ }
1141
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1142
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: TopbarComponent, isStandalone: true, selector: "ui-topbar", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, navigationId: { classPropertyName: "navigationId", publicName: "navigationId", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, autoRegister: { classPropertyName: "autoRegister", publicName: "autoRegister", isSignal: true, isRequired: false, transformFunction: null }, showHamburger: { classPropertyName: "showHamburger", publicName: "showHamburger", isSignal: true, isRequired: false, transformFunction: null }, hamburgerLabel: { classPropertyName: "hamburgerLabel", publicName: "hamburgerLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "ariaLabel()", "class": "hostClasses()", "attr.data-appearance": "appearance()" } }, viewQueries: [{ propertyName: "dropdownTpl", first: true, predicate: ["dropdownTpl"], descendants: true, isSignal: true }, { propertyName: "megaTpl", first: true, predicate: ["megaTpl"], descendants: true, isSignal: true }], ngImport: i0, template: `
1143
+ <div class="flex h-14 w-full items-center gap-3 px-3">
1144
+ <div data-ui-topbar-slot="start" class="flex shrink-0 items-center gap-2">
1145
+ @if (showHamburger()) {
1146
+ <button
1147
+ type="button"
1148
+ class="inline-flex h-9 w-9 items-center justify-center rounded-md text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:hidden"
1149
+ [attr.aria-label]="hamburgerLabel()"
1150
+ [attr.aria-expanded]="nav.mobileOpen()"
1151
+ (click)="nav.toggleMobile()">
1152
+ <ui-nav-icon name="menu" [size]="18" />
1153
+ </button>
1154
+ }
1155
+ <ng-content select="[ui-topbar-start]" />
1156
+ </div>
1157
+
1158
+ <div data-ui-topbar-slot="nav" class="flex min-w-0 flex-1 items-center justify-center">
1159
+ <ul
1160
+ class="flex min-w-0 flex-1 items-center justify-center gap-1"
1161
+ role="menubar"
1162
+ (keydown)="onMenubarKeydown($event)">
1163
+ @for (item of resolvedItems(); track item.id) {
1164
+ <li role="none" class="relative">
1165
+ @switch (item.type) {
1166
+ @case ('basic') {
1167
+ @let basic = asBasic(item);
1168
+ <a
1169
+ role="menuitem"
1170
+ class="ui-nav-text inline-flex items-center gap-2 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-primary"
1171
+ [routerLink]="basic.link"
1172
+ routerLinkActive
1173
+ #rla="routerLinkActive"
1174
+ [class.text-primary]="rla.isActive"
1175
+ [attr.aria-current]="rla.isActive ? 'page' : null"
1176
+ [target]="basic.target ?? undefined">
1177
+ @if (basic.icon) {
1178
+ <ui-nav-icon [name]="basic.icon" [size]="18" />
1179
+ }
1180
+ <span>{{ basic.title }}</span>
1181
+ </a>
1182
+ }
1183
+ @case ('collapsable') {
1184
+ @let col = asCollapsable(item);
1185
+ <button
1186
+ #trigger
1187
+ type="button"
1188
+ role="menuitem"
1189
+ class="ui-nav-text inline-flex items-center gap-2 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1190
+ [class.text-primary]="isItemActive(col.id)"
1191
+ [attr.aria-expanded]="openId() === col.id"
1192
+ [attr.aria-haspopup]="'menu'"
1193
+ (click)="toggleDropdown(trigger, item)"
1194
+ (mouseenter)="openDropdown(trigger, item)">
1195
+ @if (col.icon) {
1196
+ <ui-nav-icon [name]="col.icon" [size]="18" />
1197
+ }
1198
+ <span>{{ col.title }}</span>
1199
+ <ui-nav-icon name="expand_more" [size]="18" />
1200
+ </button>
1201
+ }
1202
+ @case ('group') {
1203
+ @let group = asGroup(item);
1204
+ <button
1205
+ #trigger
1206
+ type="button"
1207
+ role="menuitem"
1208
+ class="ui-nav-text inline-flex items-center gap-2 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1209
+ [class.text-primary]="isItemActive(group.id)"
1210
+ [attr.aria-expanded]="openId() === group.id"
1211
+ [attr.aria-haspopup]="'menu'"
1212
+ (click)="toggleDropdown(trigger, item)"
1213
+ (mouseenter)="openDropdown(trigger, item)">
1214
+ @if (group.icon) {
1215
+ <ui-nav-icon [name]="group.icon" [size]="18" />
1216
+ }
1217
+ <span>{{ group.title }}</span>
1218
+ <ui-nav-icon name="expand_more" [size]="18" />
1219
+ </button>
1220
+ }
1221
+ @case ('mega') {
1222
+ @let mega = asMega(item);
1223
+ <button
1224
+ #trigger
1225
+ type="button"
1226
+ role="menuitem"
1227
+ class="ui-nav-text inline-flex items-center gap-2 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1228
+ [class.text-primary]="isItemActive(mega.id)"
1229
+ [attr.aria-expanded]="openId() === mega.id"
1230
+ [attr.aria-haspopup]="'menu'"
1231
+ (click)="toggleMega(trigger, item)"
1232
+ (mouseenter)="openMega(trigger, item)">
1233
+ @if (mega.icon) {
1234
+ <ui-nav-icon [name]="mega.icon" [size]="18" />
1235
+ }
1236
+ <span>{{ mega.title }}</span>
1237
+ <ui-nav-icon name="expand_more" [size]="18" />
1238
+ </button>
1239
+ }
1240
+ @default {
1241
+ <span class="ui-nav-heading px-3 py-2 text-muted-foreground">
1242
+ {{ item.title }}
1243
+ </span>
1244
+ }
1245
+ }
1246
+ </li>
1247
+ }
1248
+ </ul>
1249
+ </div>
1250
+
1251
+ <div data-ui-topbar-slot="end" class="flex shrink-0 items-center justify-end gap-2">
1252
+ <ng-content select="[ui-topbar-end]" />
1253
+ </div>
1254
+ </div>
1255
+
1256
+ <!-- Dropdown template -->
1257
+ <ng-template #dropdownTpl let-item>
1258
+ <div
1259
+ role="menu"
1260
+ class="min-w-56 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"
1261
+ (keydown)="onPanelKeydown($event)">
1262
+ @for (child of item.children; track child.id) {
1263
+ <ui-nav-item [item]="child" />
1264
+ }
1265
+ </div>
1266
+ </ng-template>
1267
+
1268
+ <!-- Mega panel template -->
1269
+ <ng-template #megaTpl let-item>
1270
+ <div
1271
+ role="menu"
1272
+ class="w-screen max-w-[min(90vw,72rem)] rounded-md border border-border bg-popover p-6 text-popover-foreground shadow-lg"
1273
+ (keydown)="onPanelKeydown($event)">
1274
+ <div class="grid gap-6" [ngClass]="megaColsClass(item.columns)">
1275
+ @for (col of item.children; track col.id) {
1276
+ <div>
1277
+ <div class="ui-nav-heading mb-2 text-muted-foreground">
1278
+ {{ col.title }}
1279
+ </div>
1280
+ <div class="flex flex-col gap-0.5">
1281
+ @for (leaf of col.children ?? []; track leaf.id) {
1282
+ <ui-nav-item [item]="leaf" />
1283
+ }
1284
+ </div>
1285
+ </div>
1286
+ }
1287
+ </div>
1288
+ </div>
1289
+ </ng-template>
1290
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: UiNavIconComponent, selector: "ui-nav-icon", inputs: ["name", "class", "size"] }, { kind: "component", type: UiNavItemComponent, selector: "ui-nav-item", inputs: ["item", "level", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
459
1291
  }
460
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NavigationService, decorators: [{
461
- type: Injectable,
462
- args: [{ providedIn: 'root' }]
463
- }] });
1292
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TopbarComponent, decorators: [{
1293
+ type: Component,
1294
+ args: [{
1295
+ selector: 'ui-topbar',
1296
+ changeDetection: ChangeDetectionStrategy.OnPush,
1297
+ imports: [NgClass, RouterLink, RouterLinkActive, UiNavIconComponent, UiNavItemComponent],
1298
+ host: {
1299
+ role: 'navigation',
1300
+ '[attr.aria-label]': 'ariaLabel()',
1301
+ '[class]': 'hostClasses()',
1302
+ '[attr.data-appearance]': 'appearance()',
1303
+ },
1304
+ template: `
1305
+ <div class="flex h-14 w-full items-center gap-3 px-3">
1306
+ <div data-ui-topbar-slot="start" class="flex shrink-0 items-center gap-2">
1307
+ @if (showHamburger()) {
1308
+ <button
1309
+ type="button"
1310
+ class="inline-flex h-9 w-9 items-center justify-center rounded-md text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:hidden"
1311
+ [attr.aria-label]="hamburgerLabel()"
1312
+ [attr.aria-expanded]="nav.mobileOpen()"
1313
+ (click)="nav.toggleMobile()">
1314
+ <ui-nav-icon name="menu" [size]="18" />
1315
+ </button>
1316
+ }
1317
+ <ng-content select="[ui-topbar-start]" />
1318
+ </div>
1319
+
1320
+ <div data-ui-topbar-slot="nav" class="flex min-w-0 flex-1 items-center justify-center">
1321
+ <ul
1322
+ class="flex min-w-0 flex-1 items-center justify-center gap-1"
1323
+ role="menubar"
1324
+ (keydown)="onMenubarKeydown($event)">
1325
+ @for (item of resolvedItems(); track item.id) {
1326
+ <li role="none" class="relative">
1327
+ @switch (item.type) {
1328
+ @case ('basic') {
1329
+ @let basic = asBasic(item);
1330
+ <a
1331
+ role="menuitem"
1332
+ class="ui-nav-text inline-flex items-center gap-2 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-primary"
1333
+ [routerLink]="basic.link"
1334
+ routerLinkActive
1335
+ #rla="routerLinkActive"
1336
+ [class.text-primary]="rla.isActive"
1337
+ [attr.aria-current]="rla.isActive ? 'page' : null"
1338
+ [target]="basic.target ?? undefined">
1339
+ @if (basic.icon) {
1340
+ <ui-nav-icon [name]="basic.icon" [size]="18" />
1341
+ }
1342
+ <span>{{ basic.title }}</span>
1343
+ </a>
1344
+ }
1345
+ @case ('collapsable') {
1346
+ @let col = asCollapsable(item);
1347
+ <button
1348
+ #trigger
1349
+ type="button"
1350
+ role="menuitem"
1351
+ class="ui-nav-text inline-flex items-center gap-2 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1352
+ [class.text-primary]="isItemActive(col.id)"
1353
+ [attr.aria-expanded]="openId() === col.id"
1354
+ [attr.aria-haspopup]="'menu'"
1355
+ (click)="toggleDropdown(trigger, item)"
1356
+ (mouseenter)="openDropdown(trigger, item)">
1357
+ @if (col.icon) {
1358
+ <ui-nav-icon [name]="col.icon" [size]="18" />
1359
+ }
1360
+ <span>{{ col.title }}</span>
1361
+ <ui-nav-icon name="expand_more" [size]="18" />
1362
+ </button>
1363
+ }
1364
+ @case ('group') {
1365
+ @let group = asGroup(item);
1366
+ <button
1367
+ #trigger
1368
+ type="button"
1369
+ role="menuitem"
1370
+ class="ui-nav-text inline-flex items-center gap-2 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1371
+ [class.text-primary]="isItemActive(group.id)"
1372
+ [attr.aria-expanded]="openId() === group.id"
1373
+ [attr.aria-haspopup]="'menu'"
1374
+ (click)="toggleDropdown(trigger, item)"
1375
+ (mouseenter)="openDropdown(trigger, item)">
1376
+ @if (group.icon) {
1377
+ <ui-nav-icon [name]="group.icon" [size]="18" />
1378
+ }
1379
+ <span>{{ group.title }}</span>
1380
+ <ui-nav-icon name="expand_more" [size]="18" />
1381
+ </button>
1382
+ }
1383
+ @case ('mega') {
1384
+ @let mega = asMega(item);
1385
+ <button
1386
+ #trigger
1387
+ type="button"
1388
+ role="menuitem"
1389
+ class="ui-nav-text inline-flex items-center gap-2 rounded-md px-3 py-2 text-foreground/80 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1390
+ [class.text-primary]="isItemActive(mega.id)"
1391
+ [attr.aria-expanded]="openId() === mega.id"
1392
+ [attr.aria-haspopup]="'menu'"
1393
+ (click)="toggleMega(trigger, item)"
1394
+ (mouseenter)="openMega(trigger, item)">
1395
+ @if (mega.icon) {
1396
+ <ui-nav-icon [name]="mega.icon" [size]="18" />
1397
+ }
1398
+ <span>{{ mega.title }}</span>
1399
+ <ui-nav-icon name="expand_more" [size]="18" />
1400
+ </button>
1401
+ }
1402
+ @default {
1403
+ <span class="ui-nav-heading px-3 py-2 text-muted-foreground">
1404
+ {{ item.title }}
1405
+ </span>
1406
+ }
1407
+ }
1408
+ </li>
1409
+ }
1410
+ </ul>
1411
+ </div>
1412
+
1413
+ <div data-ui-topbar-slot="end" class="flex shrink-0 items-center justify-end gap-2">
1414
+ <ng-content select="[ui-topbar-end]" />
1415
+ </div>
1416
+ </div>
464
1417
 
465
- /* Navigation state and utility exports. */
1418
+ <!-- Dropdown template -->
1419
+ <ng-template #dropdownTpl let-item>
1420
+ <div
1421
+ role="menu"
1422
+ class="min-w-56 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"
1423
+ (keydown)="onPanelKeydown($event)">
1424
+ @for (child of item.children; track child.id) {
1425
+ <ui-nav-item [item]="child" />
1426
+ }
1427
+ </div>
1428
+ </ng-template>
1429
+
1430
+ <!-- Mega panel template -->
1431
+ <ng-template #megaTpl let-item>
1432
+ <div
1433
+ role="menu"
1434
+ class="w-screen max-w-[min(90vw,72rem)] rounded-md border border-border bg-popover p-6 text-popover-foreground shadow-lg"
1435
+ (keydown)="onPanelKeydown($event)">
1436
+ <div class="grid gap-6" [ngClass]="megaColsClass(item.columns)">
1437
+ @for (col of item.children; track col.id) {
1438
+ <div>
1439
+ <div class="ui-nav-heading mb-2 text-muted-foreground">
1440
+ {{ col.title }}
1441
+ </div>
1442
+ <div class="flex flex-col gap-0.5">
1443
+ @for (leaf of col.children ?? []; track leaf.id) {
1444
+ <ui-nav-item [item]="leaf" />
1445
+ }
1446
+ </div>
1447
+ </div>
1448
+ }
1449
+ </div>
1450
+ </div>
1451
+ </ng-template>
1452
+ `,
1453
+ }]
1454
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], navigationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigationId", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], autoRegister: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoRegister", required: false }] }], showHamburger: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHamburger", required: false }] }], hamburgerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hamburgerLabel", required: false }] }], dropdownTpl: [{ type: i0.ViewChild, args: ['dropdownTpl', { isSignal: true }] }], megaTpl: [{ type: i0.ViewChild, args: ['megaTpl', { isSignal: true }] }] } });
1455
+
1456
+ const DemoNavigationData = [
1457
+ {
1458
+ id: 'documentation',
1459
+ title: 'Documentation',
1460
+ subtitle: 'Guides and getting started resources',
1461
+ type: 'group',
1462
+ icon: 'menu_book',
1463
+ children: [
1464
+ {
1465
+ id: 'documentation-introduction',
1466
+ title: 'Introduction',
1467
+ type: 'basic',
1468
+ icon: 'menu_book',
1469
+ link: '/docs/introduction',
1470
+ },
1471
+ {
1472
+ id: 'documentation-installation',
1473
+ title: 'Installation',
1474
+ type: 'basic',
1475
+ icon: 'download',
1476
+ link: '/docs/installation',
1477
+ },
1478
+ {
1479
+ id: 'documentation-getting-started',
1480
+ title: 'Getting started',
1481
+ type: 'basic',
1482
+ icon: 'rocket_launch',
1483
+ link: '/docs/getting-started',
1484
+ },
1485
+ {
1486
+ id: 'documentation-upgrade-guide',
1487
+ title: 'Upgrade Guide',
1488
+ type: 'basic',
1489
+ icon: 'arrow_circle_up',
1490
+ link: '/docs/upgrade-guide',
1491
+ },
1492
+ {
1493
+ id: 'documentation-changelog',
1494
+ title: 'Changelog',
1495
+ type: 'basic',
1496
+ icon: 'history',
1497
+ link: '/docs/changelog',
1498
+ },
1499
+ {
1500
+ id: 'documentation-icon-lucide',
1501
+ title: 'Lucide Icons',
1502
+ type: 'basic',
1503
+ icon: 'star',
1504
+ link: '/lucide-icons',
1505
+ badge: {
1506
+ title: 'Demo',
1507
+ classes: 'ml-2 px-2 py-0.5 rounded-full text-xs bg-blue-500 text-white',
1508
+ },
1509
+ },
1510
+ ],
1511
+ },
1512
+ {
1513
+ id: 'user-interface',
1514
+ title: 'User Interface',
1515
+ subtitle: 'Components and design elements',
1516
+ type: 'group',
1517
+ icon: 'dashboard',
1518
+ children: [
1519
+ {
1520
+ id: 'user-interface-material-components',
1521
+ title: 'Material Components',
1522
+ type: 'collapsable',
1523
+ icon: 'deployed_code',
1524
+ children: [
1525
+ {
1526
+ id: 'user-interface-material-components-inputs-forms',
1527
+ title: 'Inputs & Forms',
1528
+ type: 'collapsable',
1529
+ icon: 'input',
1530
+ children: [
1531
+ {
1532
+ id: 'user-interface-material-components-inputs-forms-autocomplete',
1533
+ title: 'Autocomplete',
1534
+ type: 'basic',
1535
+ icon: 'search',
1536
+ link: '/ui/material/autocomplete',
1537
+ },
1538
+ {
1539
+ id: 'user-interface-material-components-inputs-forms-checkbox',
1540
+ title: 'Checkbox',
1541
+ type: 'basic',
1542
+ icon: 'check_box',
1543
+ link: '/ui/material/checkbox',
1544
+ },
1545
+ {
1546
+ id: 'user-interface-material-components-inputs-forms-datepicker',
1547
+ title: 'Datepicker',
1548
+ type: 'basic',
1549
+ icon: 'calendar_today',
1550
+ link: '/ui/material/datepicker',
1551
+ },
1552
+ {
1553
+ id: 'user-interface-material-components-inputs-forms-form-field',
1554
+ title: 'Form field',
1555
+ type: 'basic',
1556
+ icon: 'text_fields',
1557
+ link: '/ui/material/form-field',
1558
+ },
1559
+ {
1560
+ id: 'user-interface-material-components-inputs-forms-input',
1561
+ title: 'Input',
1562
+ type: 'basic',
1563
+ icon: 'text_fields',
1564
+ link: '/ui/material/input',
1565
+ },
1566
+ {
1567
+ id: 'user-interface-material-components-inputs-forms-radio-button',
1568
+ title: 'Radio button',
1569
+ type: 'basic',
1570
+ icon: 'radio_button_checked',
1571
+ link: '/ui/material/radio',
1572
+ },
1573
+ {
1574
+ id: 'user-interface-material-components-inputs-forms-select',
1575
+ title: 'Select',
1576
+ type: 'basic',
1577
+ icon: 'expand_more',
1578
+ link: '/ui/material/select',
1579
+ },
1580
+ {
1581
+ id: 'user-interface-material-components-inputs-forms-slider',
1582
+ title: 'Slider',
1583
+ type: 'basic',
1584
+ icon: 'tune',
1585
+ link: '/ui/material/slider',
1586
+ },
1587
+ {
1588
+ id: 'user-interface-material-components-inputs-forms-slide-toggle',
1589
+ title: 'Slide toggle',
1590
+ type: 'basic',
1591
+ icon: 'toggle_on',
1592
+ link: '/ui/material/slide-toggle',
1593
+ },
1594
+ ],
1595
+ },
1596
+ {
1597
+ id: 'user-interface-material-components-buttons-actions',
1598
+ title: 'Buttons & Actions',
1599
+ type: 'collapsable',
1600
+ icon: 'ads_click',
1601
+ children: [
1602
+ {
1603
+ id: 'user-interface-material-components-buttons-actions-button',
1604
+ title: 'Button',
1605
+ type: 'basic',
1606
+ icon: 'crop_square',
1607
+ link: '/ui/material/button',
1608
+ },
1609
+ {
1610
+ id: 'user-interface-material-components-buttons-actions-button-toggle',
1611
+ title: 'Button toggle',
1612
+ type: 'basic',
1613
+ icon: 'view_column',
1614
+ link: '/ui/material/button-toggle',
1615
+ },
1616
+ {
1617
+ id: 'user-interface-material-components-buttons-actions-chips',
1618
+ title: 'Chips',
1619
+ type: 'basic',
1620
+ icon: 'label',
1621
+ link: '/ui/material/chips',
1622
+ badge: {
1623
+ title: 'New',
1624
+ classes: 'ml-2 px-2 py-0.5 rounded-full text-xs bg-green-500 text-white',
1625
+ },
1626
+ },
1627
+ {
1628
+ id: 'user-interface-material-components-buttons-actions-menu',
1629
+ title: 'Menu',
1630
+ type: 'basic',
1631
+ icon: 'menu',
1632
+ link: '/ui/material/menu',
1633
+ },
1634
+ {
1635
+ id: 'user-interface-material-components-buttons-actions-ripples',
1636
+ title: 'Ripples',
1637
+ type: 'basic',
1638
+ icon: 'waves',
1639
+ link: '/ui/material/ripples',
1640
+ },
1641
+ ],
1642
+ },
1643
+ {
1644
+ id: 'user-interface-material-components-layout-containers',
1645
+ title: 'Layout & Containers',
1646
+ type: 'collapsable',
1647
+ icon: 'dashboard',
1648
+ children: [
1649
+ {
1650
+ id: 'user-interface-material-components-layout-containers-card',
1651
+ title: 'Card',
1652
+ type: 'basic',
1653
+ icon: 'credit_card',
1654
+ link: '/ui/material/card',
1655
+ },
1656
+ {
1657
+ id: 'user-interface-material-components-layout-containers-divider',
1658
+ title: 'Divider',
1659
+ type: 'basic',
1660
+ icon: 'remove',
1661
+ link: '/ui/material/divider',
1662
+ },
1663
+ {
1664
+ id: 'user-interface-material-components-layout-containers-expansion-panel',
1665
+ title: 'Expansion Panel',
1666
+ type: 'basic',
1667
+ icon: 'expand_more',
1668
+ link: '/ui/material/expansion',
1669
+ },
1670
+ {
1671
+ id: 'user-interface-material-components-layout-containers-grid-list',
1672
+ title: 'Grid list',
1673
+ type: 'basic',
1674
+ icon: 'grid_view',
1675
+ link: '/ui/material/grid-list',
1676
+ },
1677
+ {
1678
+ id: 'user-interface-material-components-layout-containers-list',
1679
+ title: 'List',
1680
+ type: 'basic',
1681
+ icon: 'list',
1682
+ link: '/ui/material/list',
1683
+ },
1684
+ {
1685
+ id: 'user-interface-material-components-layout-containers-sidenav',
1686
+ title: 'Sidenav',
1687
+ type: 'basic',
1688
+ icon: 'view_sidebar',
1689
+ link: '/ui/material/sidenav',
1690
+ },
1691
+ {
1692
+ id: 'user-interface-material-components-layout-containers-tabs',
1693
+ title: 'Tabs',
1694
+ type: 'basic',
1695
+ icon: 'tab',
1696
+ link: '/ui/material/tabs',
1697
+ },
1698
+ {
1699
+ id: 'user-interface-material-components-layout-containers-toolbar',
1700
+ title: 'Toolbar',
1701
+ type: 'basic',
1702
+ icon: 'build',
1703
+ link: '/ui/material/toolbar',
1704
+ },
1705
+ ],
1706
+ },
1707
+ {
1708
+ id: 'user-interface-material-components-data-display',
1709
+ title: 'Data & Display',
1710
+ type: 'collapsable',
1711
+ icon: 'bar_chart',
1712
+ children: [
1713
+ {
1714
+ id: 'user-interface-material-components-data-display-badge',
1715
+ title: 'Badge',
1716
+ type: 'basic',
1717
+ icon: 'verified',
1718
+ link: '/ui/material/badge',
1719
+ },
1720
+ {
1721
+ id: 'user-interface-material-components-data-display-icon',
1722
+ title: 'Icon',
1723
+ type: 'basic',
1724
+ icon: 'star',
1725
+ link: '/ui/material/icon',
1726
+ },
1727
+ {
1728
+ id: 'user-interface-material-components-data-display-paginator',
1729
+ title: 'Paginator',
1730
+ type: 'basic',
1731
+ icon: 'first_page',
1732
+ link: '/ui/material/paginator',
1733
+ },
1734
+ {
1735
+ id: 'user-interface-material-components-data-display-progress-bar',
1736
+ title: 'Progress bar',
1737
+ type: 'basic',
1738
+ icon: 'hourglass_empty',
1739
+ link: '/ui/material/progress-bar',
1740
+ },
1741
+ {
1742
+ id: 'user-interface-material-components-data-display-progress-spinner',
1743
+ title: 'Progress spinner',
1744
+ type: 'basic',
1745
+ icon: 'progress_activity',
1746
+ link: '/ui/material/progress-spinner',
1747
+ },
1748
+ {
1749
+ id: 'user-interface-material-components-data-display-sort',
1750
+ title: 'Sort header',
1751
+ type: 'basic',
1752
+ icon: 'swap_vert',
1753
+ link: '/ui/material/sort',
1754
+ },
1755
+ {
1756
+ id: 'user-interface-material-components-data-display-table',
1757
+ title: 'Table',
1758
+ type: 'basic',
1759
+ icon: 'table_chart',
1760
+ link: '/ui/material/table',
1761
+ },
1762
+ {
1763
+ id: 'user-interface-material-components-data-display-tree',
1764
+ title: 'Tree',
1765
+ type: 'basic',
1766
+ icon: 'hub',
1767
+ link: '/ui/material/tree',
1768
+ },
1769
+ ],
1770
+ },
1771
+ {
1772
+ id: 'user-interface-material-components-overlays-modals',
1773
+ title: 'Overlays & Modals',
1774
+ type: 'collapsable',
1775
+ icon: 'layers',
1776
+ children: [
1777
+ {
1778
+ id: 'user-interface-material-components-overlays-modals-bottom-sheet',
1779
+ title: 'Bottom Sheet',
1780
+ type: 'basic',
1781
+ icon: 'south',
1782
+ link: '/ui/material/bottom-sheet',
1783
+ },
1784
+ {
1785
+ id: 'user-interface-material-components-overlays-modals-dialog',
1786
+ title: 'Dialog',
1787
+ type: 'basic',
1788
+ icon: 'chat',
1789
+ link: '/ui/material/dialog',
1790
+ },
1791
+ {
1792
+ id: 'user-interface-material-components-overlays-modals-snack-bar',
1793
+ title: 'Snack-bar',
1794
+ type: 'basic',
1795
+ icon: 'notifications',
1796
+ link: '/ui/material/snack-bar',
1797
+ },
1798
+ {
1799
+ id: 'user-interface-material-components-overlays-modals-tooltip',
1800
+ title: 'Tooltip',
1801
+ type: 'basic',
1802
+ icon: 'help',
1803
+ link: '/ui/material/tooltip',
1804
+ },
1805
+ ],
1806
+ },
1807
+ {
1808
+ id: 'user-interface-material-components-navigation-stepper',
1809
+ title: 'Navigation & Steps',
1810
+ type: 'collapsable',
1811
+ icon: 'account_tree',
1812
+ children: [
1813
+ {
1814
+ id: 'user-interface-material-components-navigation-stepper-stepper',
1815
+ title: 'Stepper',
1816
+ type: 'basic',
1817
+ icon: 'account_tree',
1818
+ link: '/ui/material/stepper',
1819
+ },
1820
+ ],
1821
+ },
1822
+ ],
1823
+ },
1824
+ {
1825
+ id: 'user-interface-angular-cdk',
1826
+ title: 'Angular CDK',
1827
+ type: 'collapsable',
1828
+ icon: 'widgets',
1829
+ children: [
1830
+ {
1831
+ id: 'user-interface-angular-cdk-accessibility-navigation',
1832
+ title: 'Accessibility & Navigation',
1833
+ type: 'collapsable',
1834
+ icon: 'accessibility',
1835
+ children: [
1836
+ {
1837
+ id: 'user-interface-angular-cdk-accessibility-navigation-a11y',
1838
+ title: 'Accessibility',
1839
+ type: 'basic',
1840
+ icon: 'accessibility',
1841
+ link: '/ui/cdk/a11y',
1842
+ },
1843
+ {
1844
+ id: 'user-interface-angular-cdk-accessibility-navigation-focus-trap',
1845
+ title: 'Focus Trap',
1846
+ type: 'basic',
1847
+ icon: 'center_focus_strong',
1848
+ link: '/ui/cdk/focus-trap',
1849
+ },
1850
+ {
1851
+ id: 'user-interface-angular-cdk-accessibility-navigation-live-announcer',
1852
+ title: 'Live Announcer',
1853
+ type: 'basic',
1854
+ icon: 'campaign',
1855
+ link: '/ui/cdk/live-announcer',
1856
+ },
1857
+ {
1858
+ id: 'user-interface-angular-cdk-accessibility-navigation-listbox',
1859
+ title: 'Listbox',
1860
+ type: 'basic',
1861
+ icon: 'list',
1862
+ link: '/ui/cdk/listbox',
1863
+ },
1864
+ {
1865
+ id: 'user-interface-angular-cdk-accessibility-navigation-menu-cdk',
1866
+ title: 'Menu',
1867
+ type: 'basic',
1868
+ icon: 'menu',
1869
+ link: '/ui/cdk/menu',
1870
+ },
1871
+ ],
1872
+ },
1873
+ {
1874
+ id: 'user-interface-angular-cdk-layout-positioning',
1875
+ title: 'Layout & Positioning',
1876
+ type: 'collapsable',
1877
+ icon: 'dashboard',
1878
+ children: [
1879
+ {
1880
+ id: 'user-interface-angular-cdk-layout-positioning-layout',
1881
+ title: 'Layout',
1882
+ type: 'basic',
1883
+ icon: 'dashboard',
1884
+ link: '/ui/cdk/layout',
1885
+ },
1886
+ {
1887
+ id: 'user-interface-angular-cdk-layout-positioning-overlay',
1888
+ title: 'Overlay',
1889
+ type: 'basic',
1890
+ icon: 'layers',
1891
+ link: '/ui/cdk/overlay',
1892
+ },
1893
+ {
1894
+ id: 'user-interface-angular-cdk-layout-positioning-portal',
1895
+ title: 'Portal',
1896
+ type: 'basic',
1897
+ icon: 'radio_button_checked',
1898
+ link: '/ui/cdk/portal',
1899
+ },
1900
+ {
1901
+ id: 'user-interface-angular-cdk-layout-positioning-scrolling',
1902
+ title: 'Scrolling',
1903
+ type: 'basic',
1904
+ icon: 'unfold_more',
1905
+ link: '/ui/cdk/scrolling',
1906
+ },
1907
+ {
1908
+ id: 'user-interface-angular-cdk-layout-positioning-virtual-scrolling',
1909
+ title: 'Virtual Scrolling',
1910
+ type: 'basic',
1911
+ icon: 'view_agenda',
1912
+ link: '/ui/cdk/virtual-scrolling',
1913
+ },
1914
+ ],
1915
+ },
1916
+ {
1917
+ id: 'user-interface-angular-cdk-interaction-behavior',
1918
+ title: 'Interaction & Behavior',
1919
+ type: 'collapsable',
1920
+ icon: 'pan_tool',
1921
+ children: [
1922
+ {
1923
+ id: 'user-interface-angular-cdk-interaction-behavior-drag-drop',
1924
+ title: 'Drag and Drop',
1925
+ type: 'basic',
1926
+ icon: 'open_with',
1927
+ link: '/ui/cdk/drag-drop',
1928
+ },
1929
+ {
1930
+ id: 'user-interface-angular-cdk-interaction-behavior-observers',
1931
+ title: 'Observers',
1932
+ type: 'basic',
1933
+ icon: 'visibility',
1934
+ link: '/ui/cdk/observers',
1935
+ },
1936
+ {
1937
+ id: 'user-interface-angular-cdk-interaction-behavior-platform',
1938
+ title: 'Platform',
1939
+ type: 'basic',
1940
+ icon: 'devices',
1941
+ link: '/ui/cdk/platform',
1942
+ },
1943
+ {
1944
+ id: 'user-interface-angular-cdk-interaction-behavior-stepper-cdk',
1945
+ title: 'Stepper',
1946
+ type: 'basic',
1947
+ icon: 'account_tree',
1948
+ link: '/ui/cdk/stepper',
1949
+ },
1950
+ ],
1951
+ },
1952
+ {
1953
+ id: 'user-interface-angular-cdk-forms-data',
1954
+ title: 'Forms & Data',
1955
+ type: 'collapsable',
1956
+ icon: 'storage',
1957
+ children: [
1958
+ {
1959
+ id: 'user-interface-angular-cdk-forms-data-accordion',
1960
+ title: 'Accordion',
1961
+ type: 'basic',
1962
+ icon: 'expand_more',
1963
+ link: '/ui/cdk/accordion',
1964
+ },
1965
+ {
1966
+ id: 'user-interface-angular-cdk-forms-data-table-cdk',
1967
+ title: 'Table',
1968
+ type: 'basic',
1969
+ icon: 'table_chart',
1970
+ link: '/ui/cdk/table',
1971
+ },
1972
+ {
1973
+ id: 'user-interface-angular-cdk-forms-data-tree-cdk',
1974
+ title: 'Tree',
1975
+ type: 'basic',
1976
+ icon: 'hub',
1977
+ link: '/ui/cdk/tree',
1978
+ },
1979
+ ],
1980
+ },
1981
+ {
1982
+ id: 'user-interface-angular-cdk-utilities-helpers',
1983
+ title: 'Utilities & Helpers',
1984
+ type: 'collapsable',
1985
+ icon: 'build',
1986
+ children: [
1987
+ {
1988
+ id: 'user-interface-angular-cdk-utilities-helpers-bidi',
1989
+ title: 'Bidirectionality',
1990
+ type: 'basic',
1991
+ icon: 'swap_horiz',
1992
+ link: '/ui/cdk/bidi',
1993
+ },
1994
+ {
1995
+ id: 'user-interface-angular-cdk-utilities-helpers-clipboard',
1996
+ title: 'Clipboard',
1997
+ type: 'basic',
1998
+ icon: 'content_paste',
1999
+ link: '/ui/cdk/clipboard',
2000
+ },
2001
+ {
2002
+ id: 'user-interface-angular-cdk-utilities-helpers-coercion',
2003
+ title: 'Coercion',
2004
+ type: 'basic',
2005
+ icon: 'shuffle',
2006
+ link: '/ui/cdk/coercion',
2007
+ },
2008
+ {
2009
+ id: 'user-interface-angular-cdk-utilities-helpers-collections',
2010
+ title: 'Collections',
2011
+ type: 'basic',
2012
+ icon: 'folder',
2013
+ link: '/ui/cdk/collections',
2014
+ },
2015
+ {
2016
+ id: 'user-interface-angular-cdk-utilities-helpers-keycodes',
2017
+ title: 'Keycodes',
2018
+ type: 'basic',
2019
+ icon: 'keyboard',
2020
+ link: '/ui/cdk/keycodes',
2021
+ },
2022
+ ],
2023
+ },
2024
+ {
2025
+ id: 'user-interface-angular-cdk-testing-tools',
2026
+ title: 'Testing Tools',
2027
+ type: 'collapsable',
2028
+ icon: 'bug_report',
2029
+ children: [
2030
+ {
2031
+ id: 'user-interface-angular-cdk-testing-tools-component-harnesses',
2032
+ title: 'Component Harnesses',
2033
+ type: 'basic',
2034
+ icon: 'science',
2035
+ link: '/ui/cdk/testing',
2036
+ },
2037
+ {
2038
+ id: 'user-interface-angular-cdk-testing-tools-test-harnesses',
2039
+ title: 'Test Harnesses',
2040
+ type: 'basic',
2041
+ icon: 'science',
2042
+ link: '/ui/cdk/test-harnesses',
2043
+ },
2044
+ ],
2045
+ },
2046
+ {
2047
+ id: 'user-interface-angular-cdk-advanced-feature',
2048
+ title: 'Advanced feature',
2049
+ type: 'collapsable',
2050
+ icon: 'settings',
2051
+ children: [
2052
+ {
2053
+ id: 'user-interface-angular-cdk-advanced-feature-dialog-cdk',
2054
+ title: 'Dialog',
2055
+ type: 'basic',
2056
+ icon: 'chat',
2057
+ link: '/ui/cdk/dialog',
2058
+ },
2059
+ {
2060
+ id: 'user-interface-angular-cdk-advanced-feature-text-field',
2061
+ title: 'Text Field',
2062
+ type: 'basic',
2063
+ icon: 'text_fields',
2064
+ link: '/ui/cdk/text-field',
2065
+ },
2066
+ ],
2067
+ },
2068
+ ],
2069
+ },
2070
+ {
2071
+ id: 'user-interface-tailwind',
2072
+ title: 'Tailwind CSS',
2073
+ type: 'collapsable',
2074
+ icon: 'air',
2075
+ children: [
2076
+ {
2077
+ id: 'user-interface-tailwind-css-heading',
2078
+ title: 'Heading',
2079
+ type: 'basic',
2080
+ icon: 'text_fields',
2081
+ link: '/ui/tailwind/heading',
2082
+ },
2083
+ {
2084
+ id: 'user-interface-tailwind-data-display',
2085
+ title: 'Data Display',
2086
+ type: 'basic',
2087
+ icon: 'bar_chart',
2088
+ link: '/ui/tailwind/data-display',
2089
+ },
2090
+ {
2091
+ id: 'user-interface-tailwind-list',
2092
+ title: 'List',
2093
+ type: 'basic',
2094
+ icon: 'list',
2095
+ link: '/ui/tailwind/list',
2096
+ },
2097
+ {
2098
+ id: 'user-interface-tailwind-form',
2099
+ title: 'Form',
2100
+ type: 'basic',
2101
+ icon: 'dynamic_form',
2102
+ link: '/ui/tailwind/form',
2103
+ },
2104
+ {
2105
+ id: 'user-interface-tailwind-feedback',
2106
+ title: 'Feedback',
2107
+ type: 'basic',
2108
+ icon: 'report',
2109
+ link: '/ui/tailwind/feedback',
2110
+ },
2111
+ ],
2112
+ },
2113
+ ],
2114
+ },
2115
+ {
2116
+ id: 'feature',
2117
+ title: 'Features',
2118
+ subtitle: 'Advanced functionality and customization options',
2119
+ type: 'group',
2120
+ icon: 'settings',
2121
+ children: [
2122
+ {
2123
+ id: 'feature-theme',
2124
+ title: 'Themes',
2125
+ type: 'collapsable',
2126
+ icon: 'palette',
2127
+ children: [
2128
+ {
2129
+ id: 'feature-theme-dark-mode',
2130
+ title: 'Dark Mode',
2131
+ type: 'basic',
2132
+ icon: 'dark_mode',
2133
+ link: '/feature/theme/dark-mode',
2134
+ },
2135
+ {
2136
+ id: 'feature-theme-color',
2137
+ title: 'Color Schemes',
2138
+ type: 'basic',
2139
+ icon: 'palette',
2140
+ link: '/feature/theme/color-schemes',
2141
+ badge: {
2142
+ title: 'Beta',
2143
+ classes: 'ml-2 px-2 py-0.5 rounded-full text-xs bg-yellow-500 text-white',
2144
+ },
2145
+ },
2146
+ {
2147
+ id: 'feature-layout',
2148
+ title: 'Layout',
2149
+ type: 'collapsable',
2150
+ icon: 'dashboard',
2151
+ children: [
2152
+ {
2153
+ id: 'feature-layout-apps',
2154
+ title: 'Application',
2155
+ type: 'collapsable',
2156
+ icon: 'web',
2157
+ children: [
2158
+ {
2159
+ id: 'feature-layout-apps-vertical',
2160
+ title: 'Vertical',
2161
+ type: 'basic',
2162
+ icon: 'view_sidebar',
2163
+ link: '/feature/layout/application/vertical',
2164
+ },
2165
+ {
2166
+ id: 'feature-layout-apps-horizontal',
2167
+ title: 'Horizontal',
2168
+ type: 'basic',
2169
+ icon: 'view_column',
2170
+ link: '/feature/layout/application/horizontal',
2171
+ },
2172
+ ],
2173
+ },
2174
+ {
2175
+ id: 'feature-layout-page',
2176
+ title: 'Pages',
2177
+ type: 'collapsable',
2178
+ icon: 'description',
2179
+ children: [
2180
+ {
2181
+ id: 'feature-layout-page-page',
2182
+ title: 'Page',
2183
+ type: 'basic',
2184
+ icon: 'description',
2185
+ link: '/feature/layout/page/page',
2186
+ },
2187
+ {
2188
+ id: 'feature-layout-page-content',
2189
+ title: 'Content',
2190
+ type: 'basic',
2191
+ icon: 'description',
2192
+ link: '/feature/layout/page/content',
2193
+ },
2194
+ ],
2195
+ },
2196
+ ],
2197
+ },
2198
+ ],
2199
+ },
2200
+ {
2201
+ id: 'feature-navigation',
2202
+ title: 'Navigation',
2203
+ type: 'collapsable',
2204
+ icon: 'navigation',
2205
+ children: [
2206
+ {
2207
+ id: 'feature-navigation-horizontal-navigation',
2208
+ title: 'Horizontal ',
2209
+ type: 'basic',
2210
+ icon: 'view_column',
2211
+ link: '/feature/navigation/horizontal',
2212
+ },
2213
+ {
2214
+ id: 'feature-navigation-vertical-navigation',
2215
+ title: 'Vertical ',
2216
+ type: 'collapsable',
2217
+ icon: 'view_agenda',
2218
+ children: [
2219
+ {
2220
+ id: 'feature-navigation-vertical-navigation-default',
2221
+ title: 'Default',
2222
+ type: 'basic',
2223
+ icon: 'view_agenda',
2224
+ link: '/feature/navigation/vertical/default',
2225
+ },
2226
+ {
2227
+ id: 'feature-navigation-vertical-navigation-thin',
2228
+ title: 'Thin',
2229
+ type: 'basic',
2230
+ icon: 'view_agenda',
2231
+ link: '/feature/navigation/vertical/thin',
2232
+ },
2233
+ {
2234
+ id: 'feature-navigation-vertical-navigation-compact',
2235
+ title: 'Compact',
2236
+ type: 'basic',
2237
+ icon: 'view_agenda',
2238
+ link: '/feature/navigation/vertical/compact',
2239
+ },
2240
+ {
2241
+ id: 'feature-navigation-vertical-navigation-dense',
2242
+ title: 'Dense',
2243
+ type: 'basic',
2244
+ icon: 'view_agenda',
2245
+ link: '/feature/navigation/vertical/dense',
2246
+ },
2247
+ ],
2248
+ },
2249
+ ],
2250
+ },
2251
+ ],
2252
+ },
2253
+ ];
2254
+
2255
+ /*
2256
+ * Public API Surface of @ojiepermana/angular/navigation
2257
+ */
2258
+ // Core
2259
+ const NAVIGATION_VERSION = '0.0.1';
466
2260
 
467
2261
  /**
468
2262
  * Generated bundle index. Do not edit.
469
2263
  */
470
2264
 
471
- export { DEFAULT_NG_NAVIGATION_CONFIG, NG_NAVIGATION_CONFIG, NavigationPreferencesService, NavigationService, getNavigationChildren, getNavigationItemAction, getNavigationItemVisibilityHandler, hasNavigationChildren, isNavigationItemHidden, isNavigationRoutableItem, provideNgNavigation, shouldRenderNavigationItem };
2265
+ export { DEFAULT_NAVIGATION_ID, DemoNavigationData, NAVIGATION_VERSION, NavigationService, SidebarComponent, TopbarComponent, UiNavIconComponent, UiNavItemComponent };
472
2266
  //# sourceMappingURL=ojiepermana-angular-navigation.mjs.map