@ojiepermana/angular 0.1.0 → 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 (91) hide show
  1. package/README.md +41 -246
  2. package/chart/README.md +0 -0
  3. package/fesm2022/ojiepermana-angular-chart.mjs +3714 -0
  4. package/fesm2022/ojiepermana-angular-chart.mjs.map +1 -0
  5. package/fesm2022/ojiepermana-angular-component.mjs +3463 -0
  6. package/fesm2022/ojiepermana-angular-component.mjs.map +1 -0
  7. package/fesm2022/ojiepermana-angular-layout.mjs +272 -401
  8. package/fesm2022/ojiepermana-angular-layout.mjs.map +1 -1
  9. package/fesm2022/ojiepermana-angular-navigation.mjs +2225 -135
  10. package/fesm2022/ojiepermana-angular-navigation.mjs.map +1 -1
  11. package/fesm2022/ojiepermana-angular-theme.mjs +381 -1
  12. package/fesm2022/ojiepermana-angular-theme.mjs.map +1 -1
  13. package/fesm2022/ojiepermana-angular.mjs +15 -1
  14. package/fesm2022/ojiepermana-angular.mjs.map +1 -1
  15. package/package.json +49 -36
  16. package/theme/styles/etos.css +38 -0
  17. package/theme/styles/index.css +32 -8
  18. package/theme/styles/themes/brand/etos/color.css +21 -0
  19. package/theme/styles/themes/brand/etos/style.css +50 -0
  20. package/theme/styles/themes/library/_components.css +63 -0
  21. package/theme/styles/themes/library/_layers.css +15 -0
  22. package/theme/styles/themes/library/_material-overrides.css +254 -0
  23. package/theme/styles/themes/library/_tokens.css +54 -0
  24. package/theme/styles/themes/library/color/amber.css +18 -0
  25. package/theme/styles/themes/library/color/blue.css +23 -0
  26. package/theme/styles/themes/library/color/green.css +18 -0
  27. package/theme/styles/themes/library/color/index.css +9 -0
  28. package/theme/styles/themes/library/color/purple.css +18 -0
  29. package/theme/styles/themes/library/color/red.css +18 -0
  30. package/theme/styles/themes/library/style/brutal.css +47 -0
  31. package/theme/styles/themes/library/style/default.css +51 -0
  32. package/theme/styles/themes/library/style/index.css +8 -0
  33. package/theme/styles/themes/library/style/sharp.css +47 -0
  34. package/theme/styles/themes/library/style/soft.css +47 -0
  35. package/theme/styles/themes/mode/dark.css +20 -0
  36. package/theme/styles/themes/mode/index.css +6 -0
  37. package/theme/styles/themes/mode/light.css +24 -0
  38. package/theme/styles/themes/taildwind.css +109 -0
  39. package/types/ojiepermana-angular-chart.d.ts +1094 -0
  40. package/types/ojiepermana-angular-component.d.ts +1174 -0
  41. package/types/ojiepermana-angular-layout.d.ts +123 -76
  42. package/types/ojiepermana-angular-navigation.d.ts +257 -71
  43. package/types/ojiepermana-angular-theme.d.ts +170 -1
  44. package/types/ojiepermana-angular.d.ts +2 -1
  45. package/fesm2022/ojiepermana-angular-internal.mjs +0 -473
  46. package/fesm2022/ojiepermana-angular-internal.mjs.map +0 -1
  47. package/fesm2022/ojiepermana-angular-navigation-horizontal.mjs +0 -785
  48. package/fesm2022/ojiepermana-angular-navigation-horizontal.mjs.map +0 -1
  49. package/fesm2022/ojiepermana-angular-navigation-vertical.mjs +0 -1568
  50. package/fesm2022/ojiepermana-angular-navigation-vertical.mjs.map +0 -1
  51. package/fesm2022/ojiepermana-angular-shell.mjs +0 -14
  52. package/fesm2022/ojiepermana-angular-shell.mjs.map +0 -1
  53. package/fesm2022/ojiepermana-angular-theme-component.mjs +0 -235
  54. package/fesm2022/ojiepermana-angular-theme-component.mjs.map +0 -1
  55. package/fesm2022/ojiepermana-angular-theme-directive.mjs +0 -29
  56. package/fesm2022/ojiepermana-angular-theme-directive.mjs.map +0 -1
  57. package/fesm2022/ojiepermana-angular-theme-service.mjs +0 -241
  58. package/fesm2022/ojiepermana-angular-theme-service.mjs.map +0 -1
  59. package/layout/README.md +0 -144
  60. package/navigation/README.md +0 -215
  61. package/shell/README.md +0 -37
  62. package/styles/index.css +0 -2
  63. package/styles/resets.css +0 -22
  64. package/theme/README.md +0 -379
  65. package/theme/styles/adapters/material-ui/index.css +0 -205
  66. package/theme/styles/layout/horizontal.css +0 -109
  67. package/theme/styles/layout/index.css +0 -19
  68. package/theme/styles/layout/vertical.css +0 -75
  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 -89
  86. package/types/ojiepermana-angular-navigation-horizontal.d.ts +0 -77
  87. package/types/ojiepermana-angular-navigation-vertical.d.ts +0 -260
  88. package/types/ojiepermana-angular-shell.d.ts +0 -12
  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
@@ -1,176 +1,2266 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable } from '@angular/core';
3
-
4
- function getNavigationItemAction(item) {
5
- return isNavigationRoutableItem(item) ? item.action : undefined;
6
- }
7
- function getNavigationItemVisibilityHandler(item) {
8
- return item.isHidden;
9
- }
10
- function isNavigationItemHidden(item) {
11
- return !!getNavigationItemVisibilityHandler(item)?.(item);
12
- }
13
- function shouldRenderNavigationItem(item) {
14
- return !isNavigationItemHidden(item);
15
- }
16
- function hasNavigationChildren(item) {
17
- return Array.isArray(item.children) && item.children.length > 0;
18
- }
19
- function getNavigationChildren(item) {
20
- return Array.isArray(item.children) ? item.children : [];
21
- }
22
- function isNavigationRoutableItem(item) {
23
- return item.type === 'basic' || item.type === 'aside' || item.type === 'collapsable';
24
- }
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';
25
15
 
16
+ /** Default registry key used when no id is specified. */
17
+ const DEFAULT_NAVIGATION_ID = 'main';
26
18
  /**
27
- * Navigation Service
19
+ * Signal-based global state untuk navigation (sidebar/topbar).
28
20
  *
29
- * Service ini bertanggung jawab untuk:
30
- * 1. Store dan retrieve navigation data
31
- * 2. Menyediakan state aktif dan expanded item
32
- * 3. Utility functions untuk navigation operations
21
+ * Items disimpan dalam registry ber-key. Key default adalah `'main'`.
22
+ * Komponen `ui-sidebar` / `ui-topbar` memilih registry via input `navigationId`.
33
23
  */
34
24
  class NavigationService {
35
- _navigation = signal([], ...(ngDevMode ? [{ debugName: "_navigation" }] : /* istanbul ignore next */ []));
36
- _activeItemId = signal(null, ...(ngDevMode ? [{ debugName: "_activeItemId" }] : /* istanbul ignore next */ []));
37
- _expandedItemIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "_expandedItemIds" }] : /* istanbul ignore next */ []));
38
- navigationItems = this._navigation.asReadonly();
39
- activeItemId = this._activeItemId.asReadonly();
40
- flatNavigation = computed(() => this.getFlatNavigation(this._navigation()), ...(ngDevMode ? [{ debugName: "flatNavigation" }] : /* istanbul ignore next */ []));
41
- expandedItemIds = computed(() => Array.from(this._expandedItemIds()), ...(ngDevMode ? [{ debugName: "expandedItemIds" }] : /* istanbul ignore next */ []));
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();
42
31
  /**
43
- * Store navigation data
44
- * Biasanya dipanggil saat app initialization
32
+ * Backward-compatible accessor — returns items for the default (`'main'`) key.
33
+ * Prefer `getItems(id)` when working with named registries.
45
34
  */
46
- storeNavigation(navigation) {
47
- this._navigation.set(navigation);
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));
48
80
  }
49
- /**
50
- * Get navigation data dari storage
51
- */
52
- getNavigation() {
53
- return this._navigation();
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);
85
+ }
86
+ /** Remove a named registry entry. */
87
+ removeItems(id) {
88
+ this._registry.delete(id);
89
+ this._version.update((v) => v + 1);
54
90
  }
55
91
  /**
56
- * Delete navigation data dari storage
92
+ * Computed yang mengembalikan items untuk key tertentu.
93
+ * Reactive terhadap perubahan registry.
57
94
  */
58
- deleteNavigation() {
59
- if (!this._navigation().length) {
95
+ getItems(id) {
96
+ return computed(() => {
97
+ this._version(); // track changes
98
+ return this._registry.get(id) ?? [];
99
+ });
100
+ }
101
+ /** Toggle sidebar collapsed (default ↔ thin). */
102
+ toggleCollapsed() {
103
+ this.collapsed.update((v) => !v);
104
+ }
105
+ setCollapsed(value) {
106
+ this.collapsed.set(value);
107
+ }
108
+ openMobile() {
109
+ this.mobileOpen.set(true);
110
+ }
111
+ closeMobile() {
112
+ this.mobileOpen.set(false);
113
+ }
114
+ toggleMobile() {
115
+ this.mobileOpen.update((v) => !v);
116
+ }
117
+ isGroupOpen(id) {
118
+ return this.openGroups().has(id);
119
+ }
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);
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' });
142
+ }
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)
60
173
  return;
61
- }
62
- this._navigation.set([]);
63
- this.clearActiveItem();
64
- this.clearExpandedItems();
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);
65
182
  }
66
- setActiveItem(id) {
67
- this._activeItemId.set(id);
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 });
185
+ }
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));
205
+ }
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);
251
+ }
252
+ isTrailActive() {
253
+ return this.nav.isActive(this.item().id);
68
254
  }
69
- clearActiveItem() {
70
- this._activeItemId.set(null);
255
+ toggleGroup() {
256
+ const id = this.item().id;
257
+ if (id)
258
+ this.nav.toggleGroup(id);
71
259
  }
72
- getActiveItem(navigation = this._navigation()) {
73
- const activeItemId = this._activeItemId();
74
- if (!activeItemId) {
75
- return null;
260
+ runAction() {
261
+ const item = this.item();
262
+ if ('action' in item && typeof item.action === 'function') {
263
+ item.action(item);
76
264
  }
77
- return this.getItem(activeItemId, navigation);
78
265
  }
79
- expandItem(id) {
80
- this._expandedItemIds.update((expandedItemIds) => {
81
- const nextExpandedItemIds = new Set(expandedItemIds);
82
- nextExpandedItemIds.add(id);
83
- return nextExpandedItemIds;
84
- });
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
+ }
85
456
  }
86
- collapseItem(id) {
87
- this._expandedItemIds.update((expandedItemIds) => {
88
- if (!expandedItemIds.has(id)) {
89
- return expandedItemIds;
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 });
458
+ }
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>
517
+ }
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" />
528
+ }
529
+ </div>
530
+ }
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()" />
90
543
  }
91
- const nextExpandedItemIds = new Set(expandedItemIds);
92
- nextExpandedItemIds.delete(id);
93
- return nextExpandedItemIds;
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>
652
+ }
653
+ }
654
+ }
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());
724
+ }
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');
744
+ }
745
+ else {
746
+ base.push('w-full');
747
+ }
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);
753
+ }
754
+ onHoverLeave() {
755
+ if (this.appearance() === 'thin')
756
+ this.hovered.set(false);
757
+ }
758
+ /** Touch fallback: tap pada strip thin (ketika belum expanded) untuk expand. */
759
+ onHostClick(event) {
760
+ if (this.appearance() !== 'thin' || this.isMobile())
761
+ return;
762
+ if (this.hovered())
763
+ return;
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);
769
+ }
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'],
94
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());
95
802
  }
96
- toggleItemExpanded(id) {
97
- if (this.isItemExpanded(id)) {
98
- this.collapseItem(id);
803
+ closeDrawer() {
804
+ if (!this.drawerRef)
99
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;
812
+ }
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 });
846
+ }
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()" />
100
880
  }
101
- this.expandItem(id);
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 }] }] } });
900
+
901
+ /**
902
+ * Horizontal navigation (topbar) — shadcn-styled.
903
+ *
904
+ * Variants:
905
+ * - `default`: horizontal list; item `collapsable` buka dropdown
906
+ * - `megamenu`: item `mega` buka panel full-width multi-kolom
907
+ */
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());
102
941
  }
103
- isItemExpanded(id) {
104
- return this._expandedItemIds().has(id);
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;
105
950
  }
106
- clearExpandedItems() {
107
- this._expandedItemIds.set(new Set());
951
+ asCollapsable(i) {
952
+ return i;
108
953
  }
109
- /**
110
- * Flatten navigation array
111
- * Convert nested structure menjadi flat array (hanya basic items)
112
- * Berguna untuk search, analytics, atau operations lainnya
113
- */
114
- getFlatNavigation(navigation, flatNavigation = []) {
115
- for (const item of navigation) {
116
- if (item.type === 'basic') {
117
- flatNavigation.push(item);
118
- }
119
- if (hasNavigationChildren(item)) {
120
- this.getFlatNavigation(getNavigationChildren(item), flatNavigation);
121
- }
954
+ asGroup(i) {
955
+ return i;
956
+ }
957
+ asMega(i) {
958
+ return i;
959
+ }
960
+ isItemActive(id) {
961
+ return this.nav.isActive(id);
962
+ }
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';
122
978
  }
123
- return flatNavigation;
124
979
  }
125
- /**
126
- * Get item by ID dari navigation tree
127
- * Recursive search untuk find item dengan id tertentu
128
- */
129
- getItem(id, navigation) {
130
- for (const item of navigation) {
131
- if (item.id === id) {
132
- return item;
133
- }
134
- if (hasNavigationChildren(item)) {
135
- const childItem = this.getItem(id, getNavigationChildren(item));
136
- if (childItem) {
137
- return childItem;
138
- }
139
- }
980
+ toggleDropdown(trigger, item) {
981
+ if (this.openId() === item.id)
982
+ this.closeAll();
983
+ else
984
+ this.openDropdown(trigger, item);
985
+ }
986
+ openDropdown(trigger, item) {
987
+ if (this.openId() === item.id)
988
+ return;
989
+ this.closeAll();
990
+ this.attach(trigger, item, this.dropdownTpl(), /*fullWidth*/ false);
991
+ }
992
+ toggleMega(trigger, item) {
993
+ if (this.openId() === item.id)
994
+ this.closeAll();
995
+ else
996
+ this.openMega(trigger, item);
997
+ }
998
+ openMega(trigger, item) {
999
+ if (this.openId() === item.id)
1000
+ return;
1001
+ this.closeAll();
1002
+ this.attach(trigger, item, this.megaTpl(), /*fullWidth*/ true);
1003
+ }
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
+ });
1062
+ }
1063
+ closeAll() {
1064
+ if (this.active) {
1065
+ this.active.ref.dispose();
1066
+ this.active = null;
140
1067
  }
141
- return null;
1068
+ this.openId.set(null);
142
1069
  }
143
- /**
144
- * Get parent dari item dengan id tertentu
145
- * Berguna untuk breadcrumb atau navigation path
146
- */
147
- getItemParent(id, navigation, parent) {
148
- for (const item of navigation) {
149
- if (item.id === id) {
150
- return parent;
151
- }
152
- if (hasNavigationChildren(item)) {
153
- const result = this.getItemParent(id, getNavigationChildren(item), item);
154
- if (result) {
155
- return result;
156
- }
157
- }
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();
1098
+ }
1099
+ }
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();
158
1129
  }
159
- return null;
160
1130
  }
161
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
162
- 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 });
163
1291
  }
164
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NavigationService, decorators: [{
165
- type: Injectable,
166
- args: [{ providedIn: 'root' }]
167
- }] });
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>
1417
+
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 }] }] } });
168
1455
 
169
- /* Navigation state and utility exports. */
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';
170
2260
 
171
2261
  /**
172
2262
  * Generated bundle index. Do not edit.
173
2263
  */
174
2264
 
175
- export { NavigationService, getNavigationChildren, getNavigationItemAction, getNavigationItemVisibilityHandler, hasNavigationChildren, isNavigationItemHidden, isNavigationRoutableItem, shouldRenderNavigationItem };
2265
+ export { DEFAULT_NAVIGATION_ID, DemoNavigationData, NAVIGATION_VERSION, NavigationService, SidebarComponent, TopbarComponent, UiNavIconComponent, UiNavItemComponent };
176
2266
  //# sourceMappingURL=ojiepermana-angular-navigation.mjs.map