@unopsitg/ux 21.0.5 → 21.0.20

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 (35) hide show
  1. package/README.md +15 -0
  2. package/assets/_card.scss +89 -2
  3. package/assets/_config.scss +3 -3
  4. package/assets/_content.scss +39 -5
  5. package/assets/_footer.scss +59 -3
  6. package/assets/_main.scss +6 -8
  7. package/assets/_responsive.scss +15 -3
  8. package/assets/_sass_variables.scss +8 -2
  9. package/assets/_search.scss +3 -3
  10. package/assets/_tabs.scss +44 -0
  11. package/assets/_tags.scss +1 -1
  12. package/assets/_topbar.scss +45 -17
  13. package/assets/_utils.scss +0 -12
  14. package/assets/layout.scss +1 -0
  15. package/assets/sidebar/_sidebar_compact.scss +1 -6
  16. package/assets/sidebar/_sidebar_horizontal.scss +97 -76
  17. package/assets/sidebar/_sidebar_slim.scss +1 -6
  18. package/assets/sidebar/_sidebar_theme_core.scss +19 -19
  19. package/assets/sidebar/themes/_dark.scss +5 -5
  20. package/assets/sidebar/themes/_light.scss +2 -2
  21. package/assets/sidebar/themes/_primary.scss +8 -8
  22. package/assets/tailwind.css +47 -18
  23. package/assets/variables/_common.scss +1 -0
  24. package/assets/variables/_light.scss +2 -2
  25. package/fesm2022/unopsitg-ux.mjs +1532 -574
  26. package/fesm2022/unopsitg-ux.mjs.map +1 -1
  27. package/package.json +1 -1
  28. package/types/unopsitg-ux.d.ts +211 -203
  29. package/assets/opp/AppLogo/AppLogo-onDark_V.svg +0 -55
  30. package/assets/opp/AppLogo/AppLogo-onLight_V.svg +0 -55
  31. package/assets/opp/AppLogo-dark-vertical.svg +0 -55
  32. package/assets/opp/drive-download-20260421T141232Z-3-001.zip +0 -0
  33. package/assets/opp/favicon/favicon.svg +0 -17
  34. package/assets/opp/logo-dark-horizontal.svg +0 -55
  35. package/assets/opp/logo-light-horizontal.svg +0 -55
@@ -3,23 +3,23 @@ import SoftBase from '@primeuix/themes/aura';
3
3
  import CrispBase from '@primeuix/themes/lara';
4
4
  import ContrastBase from '@primeuix/themes/nora';
5
5
  import * as i0 from '@angular/core';
6
- import { InjectionToken, signal, inject, computed, effect, Injectable, Component, PLATFORM_ID, model, booleanAttribute, Input, input, HostListener, ViewChild, ElementRef, ChangeDetectionStrategy, output, DestroyRef, Directive, ContentChildren } from '@angular/core';
6
+ import { InjectionToken, signal, inject, computed, effect, Injectable, Component, PLATFORM_ID, model, booleanAttribute, Input, input, ChangeDetectionStrategy, HostListener, ViewChild, ElementRef, output, DestroyRef, Directive, ContentChildren } from '@angular/core';
7
7
  import * as i1 from '@angular/router';
8
8
  import { Router, NavigationEnd, RouterModule } from '@angular/router';
9
9
  import * as i1$1 from '@angular/common';
10
- import { CommonModule, isPlatformBrowser, NgClass } from '@angular/common';
10
+ import { CommonModule, isPlatformBrowser, NgTemplateOutlet, NgClass } from '@angular/common';
11
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
11
12
  import { BehaviorSubject, filter, Subject, takeUntil } from 'rxjs';
12
- import * as i5 from '@angular/forms';
13
+ import * as i1$2 from '@angular/forms';
13
14
  import { FormsModule } from '@angular/forms';
14
15
  import { PrimeNG } from 'primeng/config';
15
16
  import * as i4 from 'primeng/drawer';
16
17
  import { DrawerModule } from 'primeng/drawer';
17
- import * as i5$1 from 'primeng/radiobutton';
18
+ import * as i5 from 'primeng/radiobutton';
18
19
  import { RadioButtonModule } from 'primeng/radiobutton';
19
20
  import * as i3 from 'primeng/selectbutton';
20
21
  import { SelectButtonModule } from 'primeng/selectbutton';
21
- import { ToggleSwitchModule } from 'primeng/toggleswitch';
22
- import * as i6 from 'primeng/button';
22
+ import * as i2$1 from 'primeng/button';
23
23
  import { ButtonModule } from 'primeng/button';
24
24
  import * as i2 from 'primeng/divider';
25
25
  import { DividerModule } from 'primeng/divider';
@@ -29,28 +29,42 @@ import * as i3$1 from 'primeng/select';
29
29
  import { SelectModule } from 'primeng/select';
30
30
  import * as i3$2 from 'primeng/autofocus';
31
31
  import { AutoFocusModule } from 'primeng/autofocus';
32
- import * as i1$2 from 'primeng/dialog';
32
+ import * as i1$3 from 'primeng/dialog';
33
33
  import { DialogModule } from 'primeng/dialog';
34
34
  import * as i4$2 from 'primeng/ripple';
35
35
  import { RippleModule } from 'primeng/ripple';
36
36
  import * as i3$3 from 'primeng/tooltip';
37
37
  import { TooltipModule } from 'primeng/tooltip';
38
- import * as i10 from 'primeng/avatar';
38
+ import * as i9 from 'primeng/avatar';
39
39
  import { AvatarModule } from 'primeng/avatar';
40
- import * as i8 from 'primeng/badge';
40
+ import * as i7 from 'primeng/badge';
41
41
  import { BadgeModule } from 'primeng/badge';
42
- import * as i5$2 from 'primeng/iconfield';
42
+ import * as i4$3 from 'primeng/iconfield';
43
43
  import { IconFieldModule } from 'primeng/iconfield';
44
- import * as i6$1 from 'primeng/inputicon';
44
+ import * as i5$1 from 'primeng/inputicon';
45
45
  import { InputIconModule } from 'primeng/inputicon';
46
- import * as i9 from 'primeng/overlaybadge';
46
+ import * as i8 from 'primeng/overlaybadge';
47
47
  import { OverlayBadgeModule } from 'primeng/overlaybadge';
48
- import * as i3$4 from 'primeng/styleclass';
49
- import { StyleClassModule } from 'primeng/styleclass';
50
- import * as i2$1 from 'primeng/paginator';
48
+ import * as i2$2 from 'primeng/paginator';
51
49
  import { PaginatorModule } from 'primeng/paginator';
52
- import * as i2$2 from 'primeng/tabs';
50
+ import * as i4$4 from 'primeng/tabs';
53
51
  import { TabsModule } from 'primeng/tabs';
52
+ import * as i3$4 from 'primeng/fileupload';
53
+ import { FileUploadModule } from 'primeng/fileupload';
54
+ import * as i7$1 from 'primeng/menu';
55
+ import { MenuModule } from 'primeng/menu';
56
+ import * as i10 from 'primeng/panel';
57
+ import { PanelModule } from 'primeng/panel';
58
+ import * as i8$1 from 'primeng/table';
59
+ import { TableModule } from 'primeng/table';
60
+ import * as i9$1 from 'primeng/tag';
61
+ import { TagModule } from 'primeng/tag';
62
+ import * as i8$2 from 'primeng/autocomplete';
63
+ import { AutoCompleteModule } from 'primeng/autocomplete';
64
+ import * as i7$2 from 'primeng/datepicker';
65
+ import { DatePickerModule } from 'primeng/datepicker';
66
+ import * as i5$2 from 'primeng/textarea';
67
+ import { TextareaModule } from 'primeng/textarea';
54
68
 
55
69
  /**
56
70
  * Brand theme presets built on top of PrimeUIX base presets.
@@ -61,8 +75,8 @@ import { TabsModule } from 'primeng/tabs';
61
75
  * Contrast <- @primeuix/themes/nora
62
76
  */
63
77
  const brandPrimitives = {
64
- deepsea: { 50: '#F1F5F9', 100: '#E2E8F0', 200: '#CBD5E1', 300: '#94A3B8', 400: '#64748B', 500: '#475569', 600: '#334155', 700: '#1E293B', 800: '#0F172A', 900: '#0A101D', 950: '#03080c' },
65
- gray: { 50: '#f0f3f4', 100: '#ECF0F5', 200: '#d8dadf', 300: '#b9bac1', 400: '#858c99', 500: '#808393', 600: '#808284', 700: '#6a6b6d', 800: '#535455', 900: '#3c3d3e', 950: '#262627' },
78
+ deepsea: { 50: '#c3c7cb', 100: '#9ea5ac', 200: '#7a838d', 300: '#56626d', 400: '#31404e', 500: '#0d1e2f', 600: '#0b1a28', 700: '#091521', 800: '#07111a', 900: '#050c13', 950: '#03080c' },
79
+ gray: { 50: '#f8fafc', 100: '#f1f5f9', 200: '#e2e8f0', 300: '#cbd5e1', 400: '#94a3b8', 500: '#64748b', 600: '#475569', 700: '#334155', 800: '#1e293b', 900: '#0f172a', 950: '#020617' },
66
80
  red: { 50: '#f6cac6', 100: '#f0a9a4', 200: '#eb8982', 300: '#e56960', 400: '#e0493e', 500: '#da291c', 600: '#b92318', 700: '#991d14', 800: '#78170f', 900: '#57100b', 950: '#370a07' },
67
81
  orange: { 50: '#f9d6c3', 100: '#f6be9f', 200: '#f2a57a', 300: '#ef8d56', 400: '#eb7432', 500: '#e85c0e', 600: '#c54e0c', 700: '#a2400a', 800: '#803308', 900: '#5d2506', 950: '#3a1704' },
68
82
  yellow: { 50: '#fff0c5', 100: '#ffe7a1', 200: '#ffdd7e', 300: '#ffd45b', 400: '#ffcb38', 500: '#ffc215', 600: '#d9a512', 700: '#b3880f', 800: '#8c6b0c', 900: '#664e08', 950: '#403105' },
@@ -76,7 +90,8 @@ const brandPrimitives = {
76
90
  blue: { 50: '#DEEFFF', 100: '#C9E8FF', 150: '#99D3ED', 200: '#73c3e6', 300: '#4db3df', 400: '#26a2d8', 500: '#0092d1', 600: '#007cb2', 700: '#006692', 800: '#005073', 900: '#003a54', 950: '#002534' },
77
91
  darkblue: { 50: '#D0EEFF', 100: '#B7E2F9', 200: '#73abc7', 300: '#4d94b8', 400: '#267da9', 500: '#00669a', 600: '#005783', 700: '#00476c', 800: '#003855', 900: '#00293e', 950: '#001a27' },
78
92
  midnight: { 50: '#bfd2dd', 100: '#99b6c8', 200: '#739bb4', 300: '#4d809f', 400: '#26648b', 500: '#004976', 600: '#003e64', 700: '#003353', 800: '#002841', 850: '#001E31', 900: '#001d2f', 950: '#00121e' },
79
- cherry: { 50: '#e6c7d9', 100: '#d6a5c2', 200: '#c783ab', 300: '#b86294', 400: '#a8407d', 500: '#991e66', 600: '#821a57', 700: '#6b1547', 800: '#541138', 900: '#3d0c29', 950: '#26081a' }
93
+ cherry: { 50: '#e6c7d9', 100: '#d6a5c2', 200: '#c783ab', 300: '#b86294', 400: '#a8407d', 500: '#991e66', 600: '#821a57', 700: '#6b1547', 800: '#541138', 900: '#3d0c29', 950: '#26081a' },
94
+ ai: { 50: '#f0edff', 100: '#e4deff', 200: '#cdc2ff', 300: '#b0a0ff', 400: '#9378ff', 500: '#7c5cfc', 600: '#6a44f0', 700: '#5835d4', 800: '#482dac', 900: '#3b278a', 950: '#24185e' }
80
95
  };
81
96
  const brandOverrides = {
82
97
  primitive: {
@@ -115,17 +130,17 @@ const brandOverrides = {
115
130
  light: {
116
131
  surface: {
117
132
  0: '#ffffff',
118
- 50: '{deepsea.50}',
119
- 100: '{deepsea.100}',
120
- 200: '{deepsea.200}',
121
- 300: '{deepsea.300}',
122
- 400: '{deepsea.400}',
123
- 500: '{deepsea.500}',
124
- 600: '{deepsea.600}',
125
- 700: '{deepsea.700}',
126
- 800: '{deepsea.800}',
127
- 900: '{deepsea.900}',
128
- 950: '{deepsea.950}'
133
+ 50: '{gray.50}',
134
+ 100: '{gray.100}',
135
+ 200: '{gray.200}',
136
+ 300: '{gray.300}',
137
+ 400: '{gray.400}',
138
+ 500: '{gray.500}',
139
+ 600: '{gray.600}',
140
+ 700: '{gray.700}',
141
+ 800: '{gray.800}',
142
+ 900: '{gray.900}',
143
+ 950: '{gray.950}'
129
144
  }
130
145
  },
131
146
  dark: {
@@ -135,8 +150,8 @@ const brandOverrides = {
135
150
  100: '{darkblue.100}',
136
151
  200: '{darkblue.200}',
137
152
  300: '{darkblue.300}',
138
- 400: '{darkblue.400}',
139
- 500: '{darkblue.500}',
153
+ 400: '{blue.400}',
154
+ 500: '{blue.500}',
140
155
  600: '{darkblue.600}',
141
156
  700: '{darkblue.700}',
142
157
  800: '{darkblue.800}',
@@ -147,6 +162,12 @@ const brandOverrides = {
147
162
  }
148
163
  },
149
164
  components: {
165
+ accordion: {
166
+ root: { transitionDuration: '{transition.duration}' },
167
+ panel: { borderWidth: '0' },
168
+ header: { background: 'transparent', padding: '0 0 0 0' },
169
+ content: { background: 'transparent' }
170
+ },
150
171
  button: {
151
172
  colorScheme: {
152
173
  light: {
@@ -175,34 +196,36 @@ const brandOverrides = {
175
196
  }
176
197
  }
177
198
  },
199
+ // Tab active/hover backgrounds are set in _tabs.scss via sidebar
200
+ // `--d-menuitem-*` tokens (not expressible in the preset). Only structural
201
+ // properties that _tabs.scss does NOT override belong here.
178
202
  tabs: {
179
203
  tablist: {
180
- background: 'transparent'
204
+ background: 'transparent',
205
+ borderWidth: '0 0 1px 0'
181
206
  },
182
207
  tab: {
183
208
  background: 'transparent',
184
- hoverBackground: '{primary.50}',
185
- hoverColor: '{primary.700}',
186
- activeBackground: '{primary.100}',
187
- activeColor: '{primary.900}',
188
209
  borderColor: 'transparent',
189
- activeBorderColor: '{primary.200}',
190
- padding: '0.5rem 1rem'
210
+ activeBorderColor: 'transparent',
211
+ borderWidth: '0',
212
+ padding: '0.5rem 1rem',
213
+ margin: '0 0 0.5rem 0'
191
214
  },
192
215
  tabpanel: {
193
216
  background: 'transparent',
194
217
  padding: '0'
218
+ }
219
+ },
220
+ dataview: {
221
+ header: {
222
+ background: 'transparent',
223
+ borderWidth: '0',
224
+ padding: '0'
195
225
  },
196
- colorScheme: {
197
- dark: {
198
- tab: {
199
- hoverBackground: '{primary.900}',
200
- hoverColor: '{primary.200}',
201
- activeBackground: '{surface.800}',
202
- activeColor: '{primary.200}',
203
- activeBorderColor: '{primary.400}'
204
- }
205
- }
226
+ content: {
227
+ background: 'transparent',
228
+ padding: '0'
206
229
  }
207
230
  },
208
231
  paginator: {
@@ -215,6 +238,64 @@ const brandOverrides = {
215
238
  background: 'transparent'
216
239
  }
217
240
  },
241
+ panel: {
242
+ root: {
243
+ background: 'transparent',
244
+ borderColor: 'transparent',
245
+ borderRadius: '0'
246
+ },
247
+ header: {
248
+ background: 'transparent',
249
+ padding: '0',
250
+ borderWidth: '0'
251
+ },
252
+ toggleableHeader: {
253
+ padding: '0'
254
+ },
255
+ title: {
256
+ fontWeight: '700'
257
+ },
258
+ content: {
259
+ padding: '0'
260
+ }
261
+ },
262
+ card: {
263
+ root: {
264
+ shadow: 'none',
265
+ borderRadius: '{border.radius.xl}'
266
+ },
267
+ body: {
268
+ padding: '1.25rem',
269
+ gap: '1rem'
270
+ }
271
+ },
272
+ avatar: {
273
+ root: {
274
+ borderRadius: '9999px',
275
+ fontSize: '0.875rem'
276
+ }
277
+ },
278
+ drawer: {
279
+ content: { padding: '1.25rem' }
280
+ },
281
+ inputtext: {
282
+ root: {
283
+ borderRadius: '{border.radius.xl}',
284
+ paddingY: '0.5rem'
285
+ }
286
+ },
287
+ divider: {
288
+ horizontal: {
289
+ margin: '0',
290
+ padding: '0',
291
+ content: { padding: '0 0.5rem' }
292
+ },
293
+ vertical: {
294
+ margin: '0',
295
+ padding: '0',
296
+ content: { padding: '0.5rem 0' }
297
+ }
298
+ },
218
299
  tag: {
219
300
  root: {
220
301
  padding: '0.25rem 0.5rem',
@@ -224,22 +305,47 @@ const brandOverrides = {
224
305
  light: {
225
306
  secondary: { color: '{surface.800}' },
226
307
  success: { color: '{green.800}' },
227
- active: { color: '{green.800}' },
228
- inactive: { color: '{gray.800}' },
229
308
  info: { color: '{blue.800}' },
230
309
  warn: { color: '{orange.800}' },
231
- error: { color: '{red.800}' }
310
+ danger: { color: '{red.800}' }
232
311
  },
233
312
  dark: {
234
313
  secondary: { color: '{surface.100}' },
235
314
  success: { color: '{green.100}' },
236
- active: { color: '{green.100}' },
237
315
  info: { color: '{blue.100}' },
238
- inactive: { color: '{gray.100}' },
239
316
  warn: { color: '{orange.100}' },
240
- error: { color: '{red.100}' }
317
+ danger: { color: '{red.100}' }
241
318
  }
242
319
  }
320
+ },
321
+ toolbar: {
322
+ root: {
323
+ background: 'transparent',
324
+ borderColor: 'transparent',
325
+ borderRadius: '0',
326
+ padding: '0',
327
+ gap: '0.5rem'
328
+ }
329
+ },
330
+ progressbar: {
331
+ root: {
332
+ borderRadius: '{border.radius.lg}',
333
+ height: '0.75rem'
334
+ }
335
+ },
336
+ message: {
337
+ root: {
338
+ borderRadius: '{border.radius.lg}',
339
+ borderWidth: '0 0 0 4px'
340
+ },
341
+ content: {
342
+ padding: '0.75rem 1rem',
343
+ gap: '0.5rem'
344
+ },
345
+ text: {
346
+ fontSize: '0.875rem',
347
+ fontWeight: '500'
348
+ }
243
349
  }
244
350
  }
245
351
  };
@@ -429,6 +535,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
429
535
  }]
430
536
  }], ctorParameters: () => [] });
431
537
 
538
+ class FooterService {
539
+ content = signal(null, ...(ngDevMode ? [{ debugName: "content" }] : []));
540
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FooterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
541
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FooterService, providedIn: 'root' });
542
+ }
543
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FooterService, decorators: [{
544
+ type: Injectable,
545
+ args: [{ providedIn: 'root' }]
546
+ }] });
547
+
432
548
  class AppBreadcrumb {
433
549
  router;
434
550
  _breadcrumbs$ = new BehaviorSubject([]);
@@ -501,11 +617,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
501
617
  class AppConfigurator {
502
618
  simple = false;
503
619
  location = 'app';
504
- router = inject(Router);
505
620
  config = inject(PrimeNG);
506
621
  layoutService = inject(LayoutService);
507
622
  platformId = inject(PLATFORM_ID);
508
- primeng = inject(PrimeNG);
509
623
  presetKeys = Object.keys(brandPresets);
510
624
  themeOptions = [
511
625
  { name: 'Light', value: false },
@@ -533,42 +647,10 @@ class AppConfigurator {
533
647
  ];
534
648
  }
535
649
  }
536
- surfaces = [
537
- {
538
- name: 'gray',
539
- palette: {
540
- 0: '#ffffff',
541
- 50: '#e5e6e6',
542
- 100: '#d5d6d7',
543
- 200: '#c6c7c8',
544
- 300: '#b6b8b9',
545
- 400: '#a7a8aa',
546
- 500: '#97999b',
547
- 600: '#808284',
548
- 700: '#6a6b6d',
549
- 800: '#535455',
550
- 900: '#3c3d3e',
551
- 950: '#262627'
552
- }
553
- },
554
- {
555
- name: 'darkblue',
556
- palette: {
557
- 0: '#ffffff',
558
- 50: '#D0EEFF',
559
- 100: '#B7E2F9',
560
- 200: '#73abc7',
561
- 300: '#73abc7',
562
- 400: '#4d94b8',
563
- 500: '#267da9',
564
- 600: '#00669a',
565
- 700: '#005783',
566
- 800: '#00476c',
567
- 900: '#00293e',
568
- 950: '#001a27'
569
- }
570
- }
571
- ];
650
+ surfaces = ['gray', 'darkblue'].map(name => ({
651
+ name,
652
+ palette: { 0: '#ffffff', ...brandPrimitives[name] }
653
+ }));
572
654
  selectedPrimaryColor = computed(() => {
573
655
  return this.layoutService.layoutConfig().primary;
574
656
  }, ...(ngDevMode ? [{ debugName: "selectedPrimaryColor" }] : []));
@@ -834,12 +916,12 @@ class AppConfigurator {
834
916
  </div>
835
917
  <div *ngIf="!simple && location === 'app'" class="flex flex-col gap-2">
836
918
  <span class="text-lg text-muted-color font-semibold">Card Style</span>
837
- <p-selectbutton [ngModel]="cardStyle()" (ngModelChange)="onCardStyleChange($event)" [options]="cardStyleOptions" optionLabel="name" optionValue="value" [allowEmpty]="false" [allowEmpty]="false" />
919
+ <p-selectbutton [ngModel]="cardStyle()" (ngModelChange)="onCardStyleChange($event)" [options]="cardStyleOptions" optionLabel="name" optionValue="value" [allowEmpty]="false" />
838
920
  </div>
839
921
 
840
922
  <div *ngIf="!simple && location === 'app'" class="flex flex-col gap-2">
841
923
  <span class="text-lg text-muted-color font-semibold">Menu Theme</span>
842
- <p-selectbutton [ngModel]="menuTheme()" (ngModelChange)="onMenuThemeChange($event)" [options]="menuThemeOptions" optionLabel="name" optionValue="value" [allowEmpty]="false" [allowEmpty]="false" />
924
+ <p-selectbutton [ngModel]="menuTheme()" (ngModelChange)="onMenuThemeChange($event)" [options]="menuThemeOptions" optionLabel="name" optionValue="value" [allowEmpty]="false" />
843
925
  </div>
844
926
 
845
927
  <div *ngIf="!simple && location === 'app'">
@@ -857,30 +939,16 @@ class AppConfigurator {
857
939
  </div>
858
940
  </div>
859
941
  <div class="flex">
860
- <div class="flex items-center gap-2 w-6/12">
861
- <p-radiobutton name="menuMode" value="overlay" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('overlay')" inputId="overlay"></p-radiobutton>
862
- <label for="overlay">Overlay</label>
863
- </div>
864
942
  <div class="flex items-center gap-2 w-6/12">
865
943
  <p-radiobutton name="menuMode" value="slim" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('slim')" inputId="slim"></p-radiobutton>
866
944
  <label for="slim">Slim</label>
867
945
  </div>
868
- </div>
869
- <div class="flex">
870
946
  <div class="flex items-center gap-2 w-6/12">
871
947
  <p-radiobutton name="menuMode" value="compact" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('compact')" inputId="compact"></p-radiobutton>
872
948
  <label for="compact">Compact</label>
873
949
  </div>
874
- <div class="flex items-center gap-2 w-6/12">
875
- <p-radiobutton name="menuMode" value="reveal" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('reveal')" inputId="reveal"></p-radiobutton>
876
- <label for="reveal">Reveal</label>
877
- </div>
878
950
  </div>
879
951
  <div class="flex">
880
- <div class="flex items-center gap-2 w-6/12">
881
- <p-radiobutton name="menuMode" value="drawer" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('drawer')" inputId="drawer"></p-radiobutton>
882
- <label for="drawer">Drawer</label>
883
- </div>
884
952
  <div class="flex items-center gap-2 w-6/12">
885
953
  <p-radiobutton name="menuMode" value="horizontal" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('horizontal')" inputId="horizontal"></p-radiobutton>
886
954
  <label for="horizontal">Horizontal</label>
@@ -891,13 +959,13 @@ class AppConfigurator {
891
959
  </div>
892
960
  </div>
893
961
  </p-drawer>
894
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectButtonModule }, { kind: "component", type: i3.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i4.Drawer, selector: "p-drawer", inputs: ["appendTo", "motionOptions", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i5$1.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "styleClass", "autofocus", "binary", "variant", "size"], outputs: ["onClick", "onFocus", "onBlur"] }] });
962
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectButtonModule }, { kind: "component", type: i3.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i4.Drawer, selector: "p-drawer", inputs: ["appendTo", "motionOptions", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i5.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "styleClass", "autofocus", "binary", "variant", "size"], outputs: ["onClick", "onFocus", "onBlur"] }] });
895
963
  }
896
964
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppConfigurator, decorators: [{
897
965
  type: Component,
898
966
  args: [{
899
967
  selector: 'app-configurator',
900
- imports: [CommonModule, FormsModule, SelectButtonModule, DrawerModule, ToggleSwitchModule, RadioButtonModule],
968
+ imports: [CommonModule, FormsModule, SelectButtonModule, DrawerModule, RadioButtonModule],
901
969
  template: `
902
970
  <p-drawer [(visible)]="configSidebarVisible" position="right" [transitionOptions]="'.3s cubic-bezier(0, 0, 0.2, 1)'" styleClass="layout-config-sidebar w-80" header="Settings" appendTo="body">
903
971
  <div class="flex flex-col gap-6">
@@ -952,12 +1020,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
952
1020
  </div>
953
1021
  <div *ngIf="!simple && location === 'app'" class="flex flex-col gap-2">
954
1022
  <span class="text-lg text-muted-color font-semibold">Card Style</span>
955
- <p-selectbutton [ngModel]="cardStyle()" (ngModelChange)="onCardStyleChange($event)" [options]="cardStyleOptions" optionLabel="name" optionValue="value" [allowEmpty]="false" [allowEmpty]="false" />
1023
+ <p-selectbutton [ngModel]="cardStyle()" (ngModelChange)="onCardStyleChange($event)" [options]="cardStyleOptions" optionLabel="name" optionValue="value" [allowEmpty]="false" />
956
1024
  </div>
957
1025
 
958
1026
  <div *ngIf="!simple && location === 'app'" class="flex flex-col gap-2">
959
1027
  <span class="text-lg text-muted-color font-semibold">Menu Theme</span>
960
- <p-selectbutton [ngModel]="menuTheme()" (ngModelChange)="onMenuThemeChange($event)" [options]="menuThemeOptions" optionLabel="name" optionValue="value" [allowEmpty]="false" [allowEmpty]="false" />
1028
+ <p-selectbutton [ngModel]="menuTheme()" (ngModelChange)="onMenuThemeChange($event)" [options]="menuThemeOptions" optionLabel="name" optionValue="value" [allowEmpty]="false" />
961
1029
  </div>
962
1030
 
963
1031
  <div *ngIf="!simple && location === 'app'">
@@ -975,30 +1043,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
975
1043
  </div>
976
1044
  </div>
977
1045
  <div class="flex">
978
- <div class="flex items-center gap-2 w-6/12">
979
- <p-radiobutton name="menuMode" value="overlay" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('overlay')" inputId="overlay"></p-radiobutton>
980
- <label for="overlay">Overlay</label>
981
- </div>
982
1046
  <div class="flex items-center gap-2 w-6/12">
983
1047
  <p-radiobutton name="menuMode" value="slim" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('slim')" inputId="slim"></p-radiobutton>
984
1048
  <label for="slim">Slim</label>
985
1049
  </div>
986
- </div>
987
- <div class="flex">
988
1050
  <div class="flex items-center gap-2 w-6/12">
989
1051
  <p-radiobutton name="menuMode" value="compact" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('compact')" inputId="compact"></p-radiobutton>
990
1052
  <label for="compact">Compact</label>
991
1053
  </div>
992
- <div class="flex items-center gap-2 w-6/12">
993
- <p-radiobutton name="menuMode" value="reveal" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('reveal')" inputId="reveal"></p-radiobutton>
994
- <label for="reveal">Reveal</label>
995
- </div>
996
1054
  </div>
997
1055
  <div class="flex">
998
- <div class="flex items-center gap-2 w-6/12">
999
- <p-radiobutton name="menuMode" value="drawer" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('drawer')" inputId="drawer"></p-radiobutton>
1000
- <label for="drawer">Drawer</label>
1001
- </div>
1002
1056
  <div class="flex items-center gap-2 w-6/12">
1003
1057
  <p-radiobutton name="menuMode" value="horizontal" [ngModel]="menuMode()" (ngModelChange)="setMenuMode('horizontal')" inputId="horizontal"></p-radiobutton>
1004
1058
  <label for="horizontal">Horizontal</label>
@@ -1018,26 +1072,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1018
1072
  type: Input
1019
1073
  }], cardStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardStyle", required: false }] }, { type: i0.Output, args: ["cardStyleChange"] }] } });
1020
1074
 
1021
- class AppFooter {
1022
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
1023
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: AppFooter, isStandalone: true, selector: "[app-footer]", ngImport: i0, template: `
1024
- <footer class="layout-footer">
1025
- <span class="footer-copyright">&#169; UNOPS 2026</span>
1026
- </footer>
1027
- `, isInline: true });
1028
- }
1029
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppFooter, decorators: [{
1030
- type: Component,
1031
- args: [{
1032
- selector: '[app-footer]',
1033
- template: `
1034
- <footer class="layout-footer">
1035
- <span class="footer-copyright">&#169; UNOPS 2026</span>
1036
- </footer>
1037
- `
1038
- }]
1039
- }] });
1040
-
1041
1075
  class AppRightMenu {
1042
1076
  layoutService = inject(LayoutService);
1043
1077
  cards = [
@@ -1059,7 +1093,7 @@ class AppRightMenu {
1059
1093
  <div class="flex flex-col mt-7">
1060
1094
  <div class="flex gap-6">
1061
1095
  <div class="flex flex-col items-center">
1062
- <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05);]">
1096
+ <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-subtle">
1063
1097
  <i class="pi pi-dollar text-blue-600 text-2xl!"></i>
1064
1098
  </span>
1065
1099
  <span class="min-h-14 w-px bg-(--surface-border)"></span>
@@ -1071,7 +1105,7 @@ class AppRightMenu {
1071
1105
  </div>
1072
1106
  <div class="flex gap-6">
1073
1107
  <div class="flex flex-col items-center">
1074
- <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05);]">
1108
+ <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-subtle">
1075
1109
  <i class="pi pi-download text-orange-600 text-2xl!"></i>
1076
1110
  </span>
1077
1111
  <span class="min-h-14 w-px bg-(--surface-border)"></span>
@@ -1083,7 +1117,7 @@ class AppRightMenu {
1083
1117
  </div>
1084
1118
  <div class="flex gap-6">
1085
1119
  <div class="flex flex-col items-center">
1086
- <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05);]">
1120
+ <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-subtle">
1087
1121
  <i class="pi pi-question-circle text-violet-600 text-2xl!"></i>
1088
1122
  </span>
1089
1123
  <span class="min-h-14 w-px bg-(--surface-border)"></span>
@@ -1095,7 +1129,7 @@ class AppRightMenu {
1095
1129
  </div>
1096
1130
  <div class="flex gap-6">
1097
1131
  <div class="flex flex-col items-center">
1098
- <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05);]">
1132
+ <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-subtle">
1099
1133
  <i class="pi pi-comment text-blue-600 text-2xl!"></i>
1100
1134
  </span>
1101
1135
  </div>
@@ -1121,7 +1155,7 @@ class AppRightMenu {
1121
1155
  <p class="body-small mt-1 text-left">Track your ongoing shipments to customers.</p>
1122
1156
  <img class="w-full h-full max-h-60 object-cover border border-surface rounded-2xl mt-4" src="layout/images/sidebar-right/staticmap.png" alt="unops-ng_ux" />
1123
1157
  </div>
1124
- </p-drawer>`, isInline: true, dependencies: [{ kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i4.Drawer, selector: "p-drawer", inputs: ["appendTo", "motionOptions", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i5.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i6.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }] });
1158
+ </p-drawer>`, isInline: true, dependencies: [{ kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i4.Drawer, selector: "p-drawer", inputs: ["appendTo", "motionOptions", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }] });
1125
1159
  }
1126
1160
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppRightMenu, decorators: [{
1127
1161
  type: Component,
@@ -1134,7 +1168,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1134
1168
  <div class="flex flex-col mt-7">
1135
1169
  <div class="flex gap-6">
1136
1170
  <div class="flex flex-col items-center">
1137
- <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05);]">
1171
+ <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-subtle">
1138
1172
  <i class="pi pi-dollar text-blue-600 text-2xl!"></i>
1139
1173
  </span>
1140
1174
  <span class="min-h-14 w-px bg-(--surface-border)"></span>
@@ -1146,7 +1180,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1146
1180
  </div>
1147
1181
  <div class="flex gap-6">
1148
1182
  <div class="flex flex-col items-center">
1149
- <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05);]">
1183
+ <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-subtle">
1150
1184
  <i class="pi pi-download text-orange-600 text-2xl!"></i>
1151
1185
  </span>
1152
1186
  <span class="min-h-14 w-px bg-(--surface-border)"></span>
@@ -1158,7 +1192,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1158
1192
  </div>
1159
1193
  <div class="flex gap-6">
1160
1194
  <div class="flex flex-col items-center">
1161
- <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05);]">
1195
+ <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-subtle">
1162
1196
  <i class="pi pi-question-circle text-violet-600 text-2xl!"></i>
1163
1197
  </span>
1164
1198
  <span class="min-h-14 w-px bg-(--surface-border)"></span>
@@ -1170,7 +1204,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1170
1204
  </div>
1171
1205
  <div class="flex gap-6">
1172
1206
  <div class="flex flex-col items-center">
1173
- <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05);]">
1207
+ <span class="w-14 h-14 flex items-center justify-center border border-surface rounded-xl shadow-subtle">
1174
1208
  <i class="pi pi-comment text-blue-600 text-2xl!"></i>
1175
1209
  </span>
1176
1210
  </div>
@@ -1212,21 +1246,21 @@ class AppSearch {
1212
1246
  this.layoutService.layoutState.update((prev) => ({ ...prev, searchBarActive: _val }));
1213
1247
  }
1214
1248
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppSearch, deps: [], target: i0.ɵɵFactoryTarget.Component });
1215
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: AppSearch, isStandalone: true, selector: "[app-search]", ngImport: i0, template: ` <p-dialog [(visible)]="searchBarActive" [breakpoints]="{ '992px': '75vw', '576px': '90vw' }" modal dismissableMask styleClass="w-1/2 search-container">
1249
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: AppSearch, isStandalone: true, selector: "[app-search]", ngImport: i0, template: ` <p-dialog [(visible)]="searchBarActive" [breakpoints]="{ '1024px': '75vw', '780px': '90vw' }" modal dismissableMask styleClass="w-1/2 search-container">
1216
1250
  <ng-template #headless>
1217
1251
  <div class="w-full">
1218
1252
  <i class="pi pi-search"></i>
1219
1253
  <input pInputText type="text" [pAutoFocus]="true" class="p-inputtext search-input" placeholder="Search" (keydown.enter)="toggleSearchBar()" />
1220
1254
  </div>
1221
1255
  </ng-template>
1222
- </p-dialog>`, isInline: true, dependencies: [{ kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: AutoFocusModule }, { kind: "directive", type: i3$2.AutoFocus, selector: "[pAutoFocus]", inputs: ["pAutoFocus"] }] });
1256
+ </p-dialog>`, isInline: true, dependencies: [{ kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$3.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: AutoFocusModule }, { kind: "directive", type: i3$2.AutoFocus, selector: "[pAutoFocus]", inputs: ["pAutoFocus"] }] });
1223
1257
  }
1224
1258
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppSearch, decorators: [{
1225
1259
  type: Component,
1226
1260
  args: [{
1227
1261
  selector: '[app-search]',
1228
1262
  imports: [DialogModule, InputTextModule, AutoFocusModule],
1229
- template: ` <p-dialog [(visible)]="searchBarActive" [breakpoints]="{ '992px': '75vw', '576px': '90vw' }" modal dismissableMask styleClass="w-1/2 search-container">
1263
+ template: ` <p-dialog [(visible)]="searchBarActive" [breakpoints]="{ '1024px': '75vw', '780px': '90vw' }" modal dismissableMask styleClass="w-1/2 search-container">
1230
1264
  <ng-template #headless>
1231
1265
  <div class="w-full">
1232
1266
  <i class="pi pi-search"></i>
@@ -1237,6 +1271,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1237
1271
  }]
1238
1272
  }] });
1239
1273
 
1274
+ class FooterMainComponent {
1275
+ /** When true, always shows the default copyright — ignores FooterService content. */
1276
+ copyrightOnly = input(false, ...(ngDevMode ? [{ debugName: "copyrightOnly" }] : []));
1277
+ footerService = inject(FooterService);
1278
+ copyrightYear = new Date().getFullYear();
1279
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FooterMainComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1280
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: FooterMainComponent, isStandalone: true, selector: "ux-footer-main", inputs: { copyrightOnly: { classPropertyName: "copyrightOnly", publicName: "copyrightOnly", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "ux-footer-main" }, ngImport: i0, template: `
1281
+ <div class="footer-inner">
1282
+ @if (copyrightOnly()) {
1283
+ <span>&#169; UNOPS {{ copyrightYear }}</span>
1284
+ } @else if (footerService.content(); as tpl) {
1285
+ <ng-container [ngTemplateOutlet]="tpl" />
1286
+ }
1287
+ </div>
1288
+ `, isInline: true, styles: [":host{flex-shrink:0;display:flex;align-items:center;height:3rem;background:var(--p-primary-50);font-size:var(--font-size-xs, .75rem);line-height:1.2;color:var(--p-text-color)}:host-context(:root[class*=\"app-dark\"]){background:var(--p-primary-950);color:var(--p-surface-100)}:host(.footer-sticky){position:sticky;bottom:0;z-index:10}.footer-inner{width:100%;max-width:1540px;margin-inline:auto;padding:1rem}@media screen and (min-width:780px){.footer-inner{padding:1rem 3rem}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1289
+ }
1290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FooterMainComponent, decorators: [{
1291
+ type: Component,
1292
+ args: [{ selector: 'ux-footer-main', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: { class: 'ux-footer-main' }, template: `
1293
+ <div class="footer-inner">
1294
+ @if (copyrightOnly()) {
1295
+ <span>&#169; UNOPS {{ copyrightYear }}</span>
1296
+ } @else if (footerService.content(); as tpl) {
1297
+ <ng-container [ngTemplateOutlet]="tpl" />
1298
+ }
1299
+ </div>
1300
+ `, styles: [":host{flex-shrink:0;display:flex;align-items:center;height:3rem;background:var(--p-primary-50);font-size:var(--font-size-xs, .75rem);line-height:1.2;color:var(--p-text-color)}:host-context(:root[class*=\"app-dark\"]){background:var(--p-primary-950);color:var(--p-surface-100)}:host(.footer-sticky){position:sticky;bottom:0;z-index:10}.footer-inner{width:100%;max-width:1540px;margin-inline:auto;padding:1rem}@media screen and (min-width:780px){.footer-inner{padding:1rem 3rem}}\n"] }]
1301
+ }], propDecorators: { copyrightOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyrightOnly", required: false }] }] } });
1302
+
1240
1303
  class AppMenuitem {
1241
1304
  layoutService = inject(LayoutService);
1242
1305
  router = inject(Router);
@@ -1565,12 +1628,13 @@ class AppTopbar {
1565
1628
  isDarkTheme = computed(() => this.layoutService.isDarkTheme(), ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
1566
1629
  isSidebarPinned = computed(() => this.layoutService.isSidebarPinned(), ...(ngDevMode ? [{ debugName: "isSidebarPinned" }] : []));
1567
1630
  searchActive = signal(false, ...(ngDevMode ? [{ debugName: "searchActive" }] : []));
1568
- profileMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "profileMenuOpen" }] : []));
1631
+ activeDropdown = signal(null, ...(ngDevMode ? [{ debugName: "activeDropdown" }] : []));
1569
1632
  shouldFocusSearch = false;
1570
1633
  menuButton;
1571
1634
  searchInput;
1635
+ notificationsItem;
1636
+ languageItem;
1572
1637
  profileItem;
1573
- profilePanel;
1574
1638
  notificationsBars = signal([
1575
1639
  {
1576
1640
  id: 'inbox',
@@ -1591,21 +1655,21 @@ class AppTopbar {
1591
1655
  id: 'inbox',
1592
1656
  data: [
1593
1657
  {
1594
- image: 'demo/images/avatar/avatar-square-m-2.jpg',
1658
+ initials: 'ML',
1595
1659
  name: 'Michael Lee',
1596
1660
  description: 'You have a new message from the support team regarding your recent inquiry.',
1597
1661
  time: '1 hour ago',
1598
1662
  new: true
1599
1663
  },
1600
1664
  {
1601
- image: 'demo/images/avatar/avatar-square-f-1.jpg',
1665
+ initials: 'AJ',
1602
1666
  name: 'Alice Johnson',
1603
1667
  description: 'Your report has been successfully submitted and is under review.',
1604
1668
  time: '10 minutes ago',
1605
1669
  new: true
1606
1670
  },
1607
1671
  {
1608
- image: 'demo/images/avatar/avatar-square-f-2.jpg',
1672
+ initials: 'ED',
1609
1673
  name: 'Emily Davis',
1610
1674
  description: 'The project deadline has been updated to September 30th. Please check the details.',
1611
1675
  time: 'Yesterday at 4:35 PM',
@@ -1617,14 +1681,14 @@ class AppTopbar {
1617
1681
  id: 'general',
1618
1682
  data: [
1619
1683
  {
1620
- image: 'demo/images/avatar/avatar-square-f-1.jpg',
1684
+ initials: 'AJ',
1621
1685
  name: 'Alice Johnson',
1622
1686
  description: 'Reminder: Your subscription is about to expire in 3 days. Renew now to avoid interruption.',
1623
1687
  time: '30 minutes ago',
1624
1688
  new: true
1625
1689
  },
1626
1690
  {
1627
- image: 'demo/images/avatar/avatar-square-m-2.jpg',
1691
+ initials: 'ML',
1628
1692
  name: 'Michael Lee',
1629
1693
  description: 'The server maintenance has been completed successfully. No further downtime is expected.',
1630
1694
  time: 'Yesterday at 2:15 PM',
@@ -1636,14 +1700,14 @@ class AppTopbar {
1636
1700
  id: 'archived',
1637
1701
  data: [
1638
1702
  {
1639
- image: 'demo/images/avatar/avatar-square-m-1.jpg',
1703
+ initials: 'LB',
1640
1704
  name: 'Lucas Brown',
1641
1705
  description: 'Your appointment with Dr. Anderson has been confirmed for October 12th at 10:00 AM.',
1642
1706
  time: '1 week ago',
1643
1707
  new: true
1644
1708
  },
1645
1709
  {
1646
- image: 'demo/images/avatar/avatar-square-f-2.jpg',
1710
+ initials: 'ED',
1647
1711
  name: 'Emily Davis',
1648
1712
  description: 'The document you uploaded has been successfully archived for future reference.',
1649
1713
  time: '2 weeks ago',
@@ -1653,9 +1717,9 @@ class AppTopbar {
1653
1717
  }
1654
1718
  ], ...(ngDevMode ? [{ debugName: "notifications" }] : []));
1655
1719
  languages = signal([
1656
- { code: 'en', label: 'English', flag: '🇬🇧' },
1657
- { code: 'fr', label: 'French', flag: '🇫🇷' },
1658
- { code: 'es', label: 'Spanish', flag: '🇪🇸' }
1720
+ { code: 'en', label: 'English', flag: '\u{1F1EC}\u{1F1E7}' },
1721
+ { code: 'fr', label: 'French', flag: '\u{1F1EB}\u{1F1F7}' },
1722
+ { code: 'es', label: 'Spanish', flag: '\u{1F1EA}\u{1F1F8}' }
1659
1723
  ], ...(ngDevMode ? [{ debugName: "languages" }] : []));
1660
1724
  selectedLanguage = signal('en', ...(ngDevMode ? [{ debugName: "selectedLanguage" }] : []));
1661
1725
  selectedNotificationBar = model(this.notificationsBars()[0].id ?? 'inbox', ...(ngDevMode ? [{ debugName: "selectedNotificationBar" }] : []));
@@ -1680,18 +1744,27 @@ class AppTopbar {
1680
1744
  }
1681
1745
  selectLanguage(code) {
1682
1746
  this.selectedLanguage.set(code);
1747
+ this.closeDropdown();
1683
1748
  }
1684
- toggleProfileMenu(event) {
1749
+ toggleDropdown(id, event) {
1685
1750
  event.stopPropagation();
1686
- this.profileMenuOpen.update((open) => !open);
1751
+ this.activeDropdown.update((current) => (current === id ? null : id));
1752
+ }
1753
+ closeDropdown() {
1754
+ this.activeDropdown.set(null);
1687
1755
  }
1688
1756
  onDocumentClick(event) {
1689
- if (!this.profileMenuOpen())
1757
+ if (!this.activeDropdown())
1690
1758
  return;
1691
1759
  const target = event.target;
1692
- const insideProfile = this.profileItem?.nativeElement?.contains(target);
1693
- if (!insideProfile) {
1694
- this.profileMenuOpen.set(false);
1760
+ const containers = [
1761
+ this.notificationsItem?.nativeElement,
1762
+ this.languageItem?.nativeElement,
1763
+ this.profileItem?.nativeElement
1764
+ ];
1765
+ const insideAny = containers.some((el) => el?.contains(target));
1766
+ if (!insideAny) {
1767
+ this.closeDropdown();
1695
1768
  }
1696
1769
  }
1697
1770
  openSearch() {
@@ -1711,24 +1784,29 @@ class AppTopbar {
1711
1784
  this.layoutService.toggleSearchBar();
1712
1785
  }
1713
1786
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
1714
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: AppTopbar, isStandalone: true, selector: "[app-topbar]", inputs: { selectedNotificationBar: { classPropertyName: "selectedNotificationBar", publicName: "selectedNotificationBar", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedNotificationBar: "selectedNotificationBarChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "menuButton", first: true, predicate: ["menubutton"], descendants: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "profileItem", first: true, predicate: ["profileItem"], descendants: true }, { propertyName: "profilePanel", first: true, predicate: ["profilePanel"], descendants: true }], ngImport: i0, template: `<div class="layout-topbar">
1787
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: AppTopbar, isStandalone: true, selector: "[app-topbar]", inputs: { selectedNotificationBar: { classPropertyName: "selectedNotificationBar", publicName: "selectedNotificationBar", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedNotificationBar: "selectedNotificationBarChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "menuButton", first: true, predicate: ["menubutton"], descendants: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "notificationsItem", first: true, predicate: ["notificationsItem"], descendants: true }, { propertyName: "languageItem", first: true, predicate: ["languageItem"], descendants: true }, { propertyName: "profileItem", first: true, predicate: ["profileItem"], descendants: true }], ngImport: i0, template: `<div class="layout-topbar">
1715
1788
  <button type="button" class="mobile-menu-button" aria-label="Toggle navigation menu" (click)="onMenuButtonClick()">
1716
1789
  <i class="pi pi-bars"></i>
1717
1790
  </button>
1718
1791
  <div class="topbar-left">
1719
- <button
1720
- type="button"
1721
- class="topbar-menu-toggle"
1722
- [class.active]="isSidebarPinned()"
1723
- [attr.aria-label]="isSidebarPinned() ? 'Collapse sidebar' : 'Expand sidebar'"
1724
- (click)="toggleSidebarPin()"
1725
- >
1726
- <i class="pi pi-bars"></i>
1727
- </button>
1728
- <a class="topbar-logo" [routerLink]="['/']">
1729
- <img [src]="desktopLogo()" [attr.alt]="mobileLogoConfig.alt" />
1730
- </a>
1731
- <span class="topbar-logo-separator"></span>
1792
+ <div class="topbar-sidebar-section">
1793
+ <button
1794
+ type="button"
1795
+ class="topbar-menu-toggle"
1796
+ [class.active]="isSidebarPinned()"
1797
+ [attr.aria-label]="isSidebarPinned() ? 'Collapse sidebar' : 'Expand sidebar'"
1798
+ (click)="toggleSidebarPin()"
1799
+ >
1800
+ <i class="pi pi-bars"></i>
1801
+ </button>
1802
+ <a class="topbar-logo" [routerLink]="['/']">
1803
+ <img [src]="desktopLogo()" [attr.alt]="mobileLogoConfig.alt" />
1804
+ </a>
1805
+ <span class="topbar-logo-separator"></span>
1806
+ </div>
1807
+ </div>
1808
+
1809
+ <div class="topbar-main">
1732
1810
  <div app-breadcrumb></div>
1733
1811
  @if (searchActive()) {
1734
1812
  <div class="flex items-center gap-2 ml-auto">
@@ -1741,14 +1819,8 @@ class AppTopbar {
1741
1819
  </button>
1742
1820
  </div>
1743
1821
  }
1744
- </div>
1745
-
1746
- <a class="mobile-logo" [routerLink]="['/landing']">
1747
- <img [src]="mobileLogo()" [attr.alt]="mobileLogoConfig.alt" />
1748
- </a>
1749
-
1750
- <div class="topbar-right">
1751
- <ul class="topbar-menu">
1822
+ <div class="topbar-right">
1823
+ <ul class="topbar-menu">
1752
1824
  <li class="right-sidebar-item" [class.hidden]="searchActive()">
1753
1825
  <a class="right-sidebar-button" aria-label="Open search" (click)="openSearch()">
1754
1826
  <i class="pi pi-search"></i>
@@ -1763,56 +1835,56 @@ class AppTopbar {
1763
1835
  <i [class]="isDarkTheme() ? 'pi pi-sun' : 'pi pi-moon'"></i>
1764
1836
  </a>
1765
1837
  </li>
1766
- <li class="right-sidebar-item static sm:relative z-50">
1767
- <a class="right-sidebar-button" aria-label="Notifications" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveActiveClass="animate-fadeout" leaveToClass="hidden" [hideOnOutsideClick]="true">
1838
+ <li class="right-sidebar-item static sm:relative z-50" #notificationsItem>
1839
+ <a class="right-sidebar-button" aria-label="Notifications" (click)="toggleDropdown('notifications', $event)">
1768
1840
  <span class="w-2 h-2 rounded-full bg-red-500 absolute top-2 right-2.5"></span>
1769
1841
  <i class="pi pi-bell"></i>
1770
1842
  </a>
1771
- <div
1772
- class="list-none m-0 rounded-2xl border border-surface fixed sm:absolute bg-surface-0 dark:bg-surface-900 overflow-hidden hidden origin-top w-[calc(100vw-2rem)] sm:w-88 mt-2 z-50 top-auto left-4 sm:left-auto sm:right-0 shadow-[0px_56px_16px_0px_rgba(0,0,0,0.00),0px_36px_14px_0px_rgba(0,0,0,0.01),0px_20px_12px_0px_rgba(0,0,0,0.02),0px_9px_9px_0px_rgba(0,0,0,0.03),0px_2px_5px_0px_rgba(0,0,0,0.04)]"
1773
- >
1774
- <div class="p-4 flex items-center justify-between border-b border-surface">
1775
- <span class="label-small text-surface-950 dark:text-surface-0">Notifications</span>
1776
- <button pRipple class="py-1 px-2 text-surface-950 dark:text-surface-0 label-x-small hover:bg-emphasis border border-surface rounded-lg shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05)] transition-all">Mark all as read</button>
1777
- </div>
1778
- <div class="flex items-center border-b border-surface">
1779
- @for (item of notificationsBars(); track item.id; let i = $index) {
1780
- <button
1781
- [ngClass]="{ 'border-surface-950 dark:border-surface-0': selectedNotificationBar() === item.id, 'border-transparent': selectedNotificationBar() !== item.id }"
1782
- class="px-3.5 py-2 inline-flex items-center border-b gap-2"
1783
- (click)="selectedNotificationBar.set(item.id)"
1784
- >
1785
- <span [ngClass]="{ 'text-surface-950 dark:text-surface-0': selectedNotificationBar() === item.id }" class="label-small">{{ item.label }}</span>
1786
- <p-badge *ngIf="item?.badge" [value]="item.badge" severity="success" size="small" class="rounded-md!" />
1787
- </button>
1788
- }
1789
- </div>
1790
- <ul class="flex flex-col divide-y divide-(--surface-border) max-h-80 overflow-auto">
1791
- @for (item of selectedNotificationsBarData(); track item.name; let i = $index) {
1792
- <li>
1793
- <div class="flex items-center gap-3 px-4 sm:px-6 py-3.5 cursor-pointer hover:bg-emphasis transition-all">
1794
- <p-overlay-badge value="" severity="danger" class="inline-flex">
1795
- <p-avatar size="large">
1796
- <img [src]="item.image" [alt]="item.name" class="rounded-lg" />
1797
- </p-avatar>
1798
- </p-overlay-badge>
1799
- <div class="flex items-center gap-3">
1800
- <div class="flex flex-col">
1801
- <span class="label-small text-left text-surface-950 dark:text-surface-0">{{ item.name }}</span>
1802
- <span class="label-xsmall text-left line-clamp-1">{{ item.description }}</span>
1803
- <span class="label-xsmall text-left">{{ item.time }}</span>
1843
+ @if (activeDropdown() === 'notifications') {
1844
+ <div
1845
+ class="list-none m-0 rounded-2xl border border-surface fixed sm:absolute bg-surface-0 dark:bg-surface-900 overflow-hidden origin-top w-[calc(100vw-2rem)] sm:w-88 mt-2 z-50 top-auto left-4 sm:left-auto sm:right-0 shadow-flyout animate-scalein"
1846
+ >
1847
+ <div class="p-4 flex items-center justify-between border-b border-surface">
1848
+ <span class="label-small text-surface-950 dark:text-surface-0">Notifications</span>
1849
+ <button pRipple class="py-1 px-2 text-surface-950 dark:text-surface-0 label-x-small hover:bg-emphasis border border-surface rounded-lg shadow-subtle transition-all">Mark all as read</button>
1850
+ </div>
1851
+ <div class="flex items-center border-b border-surface">
1852
+ @for (item of notificationsBars(); track item.id; let i = $index) {
1853
+ <button
1854
+ [ngClass]="{ 'border-surface-950 dark:border-surface-0': selectedNotificationBar() === item.id, 'border-transparent': selectedNotificationBar() !== item.id }"
1855
+ class="px-3.5 py-2 inline-flex items-center border-b gap-2"
1856
+ (click)="selectedNotificationBar.set(item.id)"
1857
+ >
1858
+ <span [ngClass]="{ 'text-surface-950 dark:text-surface-0': selectedNotificationBar() === item.id }" class="label-small">{{ item.label }}</span>
1859
+ <p-badge *ngIf="item?.badge" [value]="item.badge" severity="success" size="small" class="rounded-md!" />
1860
+ </button>
1861
+ }
1862
+ </div>
1863
+ <ul class="flex flex-col divide-y divide-(--surface-border) max-h-80 overflow-auto">
1864
+ @for (item of selectedNotificationsBarData(); track item.name; let i = $index) {
1865
+ <li>
1866
+ <div class="flex items-center gap-3 px-4 sm:px-6 py-3.5 cursor-pointer hover:bg-emphasis transition-all">
1867
+ <p-overlay-badge value="" severity="danger" class="inline-flex">
1868
+ <p-avatar [label]="item.initials" size="large" styleClass="rounded-lg" />
1869
+ </p-overlay-badge>
1870
+ <div class="flex items-center gap-3">
1871
+ <div class="flex flex-col">
1872
+ <span class="label-small text-left text-surface-950 dark:text-surface-0">{{ item.name }}</span>
1873
+ <span class="label-xsmall text-left line-clamp-1">{{ item.description }}</span>
1874
+ <span class="label-xsmall text-left">{{ item.time }}</span>
1875
+ </div>
1876
+ <p-badge *ngIf="item.new" value="" severity="success" />
1804
1877
  </div>
1805
- <p-badge *ngIf="item.new" value="" severity="success" />
1806
1878
  </div>
1807
- </div>
1808
- <span *ngIf="i !== notifications().length - 1"></span>
1809
- </li>
1810
- }
1811
- </ul>
1812
- </div>
1879
+ <span *ngIf="i !== notifications().length - 1"></span>
1880
+ </li>
1881
+ }
1882
+ </ul>
1883
+ </div>
1884
+ }
1813
1885
  </li>
1814
- <li class="right-sidebar-item static sm:relative">
1815
- <a class="right-sidebar-button relative z-50" aria-label="Change language" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveActiveClass="animate-fadeout" leaveToClass="hidden" [hideOnOutsideClick]="true">
1886
+ <li class="right-sidebar-item static sm:relative" #languageItem>
1887
+ <a class="right-sidebar-button relative z-50" aria-label="Change language" (click)="toggleDropdown('language', $event)">
1816
1888
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1817
1889
  <path d="m5 8 6 6"/>
1818
1890
  <path d="m4 14 6-6 2-3"/>
@@ -1822,137 +1894,149 @@ class AppTopbar {
1822
1894
  <path d="M14 18h6"/>
1823
1895
  </svg>
1824
1896
  </a>
1825
- <div
1826
- class="list-none p-2 m-0 rounded-2xl border border-surface overflow-hidden fixed sm:absolute bg-surface-0 dark:bg-surface-900 hidden origin-top w-44 mt-2 right-4 sm:right-0 z-999 top-auto shadow-[0px_56px_16px_0px_rgba(0,0,0,0.00),0px_36px_14px_0px_rgba(0,0,0,0.01),0px_20px_12px_0px_rgba(0,0,0,0.02),0px_9px_9px_0px_rgba(0,0,0,0.03),0px_2px_5px_0px_rgba(0,0,0,0.04)]"
1827
- >
1828
- <ul class="flex flex-col gap-1">
1829
- @for (lang of languages(); track lang.code) {
1830
- <li>
1831
- <a
1832
- class="label-small dark:text-surface-400 flex gap-2.5 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer"
1833
- [class.text-surface-950]="selectedLanguage() === lang.code"
1834
- [class.dark:text-surface-0]="selectedLanguage() === lang.code"
1835
- [class.font-semibold]="selectedLanguage() === lang.code"
1836
- (click)="selectLanguage(lang.code)"
1837
- >
1838
- <span class="text-lg">{{ lang.flag }}</span>
1839
- <span>{{ lang.label }}</span>
1840
- </a>
1841
- </li>
1842
- }
1843
- </ul>
1844
- </div>
1897
+ @if (activeDropdown() === 'language') {
1898
+ <div
1899
+ class="list-none p-2 m-0 rounded-2xl border border-surface overflow-hidden fixed sm:absolute bg-surface-0 dark:bg-surface-900 origin-top w-44 mt-2 right-4 sm:right-0 z-999 top-auto shadow-flyout animate-scalein"
1900
+ >
1901
+ <ul class="flex flex-col gap-1">
1902
+ @for (lang of languages(); track lang.code) {
1903
+ <li>
1904
+ <a
1905
+ class="label-small dark:text-surface-400 flex gap-2.5 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer"
1906
+ [class.text-surface-950]="selectedLanguage() === lang.code"
1907
+ [class.dark:text-surface-0]="selectedLanguage() === lang.code"
1908
+ [class.font-semibold]="selectedLanguage() === lang.code"
1909
+ (click)="selectLanguage(lang.code)"
1910
+ >
1911
+ <span class="text-lg">{{ lang.flag }}</span>
1912
+ <span>{{ lang.label }}</span>
1913
+ </a>
1914
+ </li>
1915
+ }
1916
+ </ul>
1917
+ </div>
1918
+ }
1845
1919
  </li>
1846
1920
  <li class="profile-item static sm:relative" #profileItem>
1847
- <a class="right-sidebar-button relative z-50" aria-label="User profile menu" (click)="toggleProfileMenu($event)">
1921
+ <a class="right-sidebar-button relative z-50" aria-label="User profile menu" (click)="toggleDropdown('profile', $event)">
1848
1922
  <p-avatar icon="pi pi-user" styleClass="w-10! h-10!" />
1849
1923
  </a>
1850
- <div
1851
- #profilePanel
1852
- class="list-none p-2 m-0 rounded-2xl border border-surface overflow-hidden fixed sm:absolute bg-surface-0 dark:bg-surface-900 origin-top w-52 mt-2 right-4 sm:right-0 z-999 top-auto shadow-[0px_56px_16px_0px_rgba(0,0,0,0.00),0px_36px_14px_0px_rgba(0,0,0,0.01),0px_20px_12px_0px_rgba(0,0,0,0.02),0px_9px_9px_0px_rgba(0,0,0,0.03),0px_2px_5px_0px_rgba(0,0,0,0.04)]"
1853
- [class.hidden]="!profileMenuOpen()"
1854
- [class.animate-scalein]="profileMenuOpen()"
1855
- >
1856
- <ul class="flex flex-col gap-1">
1857
- <div class="mobile-profile-actions">
1924
+ @if (activeDropdown() === 'profile') {
1925
+ <div
1926
+ #profilePanel
1927
+ class="list-none p-2 m-0 rounded-2xl border border-surface overflow-hidden fixed sm:absolute bg-surface-0 dark:bg-surface-900 origin-top w-52 mt-2 right-4 sm:right-0 z-999 top-auto shadow-flyout animate-scalein"
1928
+ >
1929
+ <ul class="flex flex-col gap-1">
1930
+ <div class="mobile-profile-actions">
1931
+ <li>
1932
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown(); openSearch()">
1933
+ <i class="pi pi-search"></i>
1934
+ <span>Search</span>
1935
+ </a>
1936
+ </li>
1937
+ <li>
1938
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown(); toggleDarkMode()">
1939
+ <i [class]="isDarkTheme() ? 'pi pi-sun' : 'pi pi-moon'"></i>
1940
+ <span>{{ isDarkTheme() ? 'Light Mode' : 'Dark Mode' }}</span>
1941
+ </a>
1942
+ </li>
1943
+ <li>
1944
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer relative" (click)="closeDropdown()">
1945
+ <i class="pi pi-bell"></i>
1946
+ <span>Notifications</span>
1947
+ <span class="w-2 h-2 rounded-full bg-red-500 ml-auto"></span>
1948
+ </a>
1949
+ </li>
1950
+ <li class="border-b border-surface pb-1 mb-1">
1951
+ <span class="label-xsmall px-2.5 py-1 text-surface-400">Language</span>
1952
+ @for (lang of languages(); track lang.code) {
1953
+ <a
1954
+ class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer"
1955
+ [class.text-surface-950]="selectedLanguage() === lang.code"
1956
+ [class.dark:text-surface-0]="selectedLanguage() === lang.code"
1957
+ [class.font-semibold]="selectedLanguage() === lang.code"
1958
+ (click)="closeDropdown(); selectLanguage(lang.code)"
1959
+ >
1960
+ <span class="text-lg">{{ lang.flag }}</span>
1961
+ <span>{{ lang.label }}</span>
1962
+ @if (selectedLanguage() === lang.code) {
1963
+ <i class="pi pi-check ml-auto text-xs"></i>
1964
+ }
1965
+ </a>
1966
+ }
1967
+ </li>
1968
+ </div>
1858
1969
  <li>
1859
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false); openSearch()">
1860
- <i class="pi pi-search"></i>
1861
- <span>Search</span>
1970
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown()">
1971
+ <i class="pi pi-user"></i>
1972
+ <span>Profile</span>
1862
1973
  </a>
1863
1974
  </li>
1864
1975
  <li>
1865
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false); toggleDarkMode()">
1866
- <i [class]="isDarkTheme() ? 'pi pi-sun' : 'pi pi-moon'"></i>
1867
- <span>{{ isDarkTheme() ? 'Light Mode' : 'Dark Mode' }}</span>
1976
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown(); onConfigButtonClick()">
1977
+ <i class="pi pi-cog"></i>
1978
+ <span>Settings</span>
1868
1979
  </a>
1869
1980
  </li>
1870
1981
  <li>
1871
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer relative" (click)="profileMenuOpen.set(false)">
1872
- <i class="pi pi-bell"></i>
1873
- <span>Notifications</span>
1874
- <span class="w-2 h-2 rounded-full bg-red-500 ml-auto"></span>
1982
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown()">
1983
+ <i class="pi pi-calendar"></i>
1984
+ <span>Calendar</span>
1875
1985
  </a>
1876
1986
  </li>
1877
- <li class="border-b border-surface pb-1 mb-1">
1878
- <span class="label-xsmall px-2.5 py-1 text-surface-400">Language</span>
1879
- @for (lang of languages(); track lang.code) {
1880
- <a
1881
- class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer"
1882
- [class.text-surface-950]="selectedLanguage() === lang.code"
1883
- [class.dark:text-surface-0]="selectedLanguage() === lang.code"
1884
- [class.font-semibold]="selectedLanguage() === lang.code"
1885
- (click)="profileMenuOpen.set(false); selectLanguage(lang.code)"
1886
- >
1887
- <span class="text-lg">{{ lang.flag }}</span>
1888
- <span>{{ lang.label }}</span>
1889
- @if (selectedLanguage() === lang.code) {
1890
- <i class="pi pi-check ml-auto text-xs"></i>
1891
- }
1892
- </a>
1893
- }
1987
+ <li>
1988
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown()">
1989
+ <i class="pi pi-inbox"></i>
1990
+ <span>Inbox</span>
1991
+ </a>
1894
1992
  </li>
1895
- </div>
1896
- <li>
1897
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
1898
- <i class="pi pi-user"></i>
1899
- <span>Profile</span>
1900
- </a>
1901
- </li>
1902
- <li>
1903
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
1904
- <i class="pi pi-cog"></i>
1905
- <span>Settings</span>
1906
- </a>
1907
- </li>
1908
- <li>
1909
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
1910
- <i class="pi pi-calendar"></i>
1911
- <span>Calendar</span>
1912
- </a>
1913
- </li>
1914
- <li>
1915
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
1916
- <i class="pi pi-inbox"></i>
1917
- <span>Inbox</span>
1918
- </a>
1919
- </li>
1920
- <li class="border-t border-surface mt-1 pt-1">
1921
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
1922
- <i class="pi pi-power-off"></i>
1923
- <span>Log out</span>
1924
- </a>
1925
- </li>
1926
- </ul>
1927
- </div>
1993
+ <li class="border-t border-surface mt-1 pt-1">
1994
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown()">
1995
+ <i class="pi pi-power-off"></i>
1996
+ <span>Log out</span>
1997
+ </a>
1998
+ </li>
1999
+ </ul>
2000
+ </div>
2001
+ }
1928
2002
  </li>
1929
- </ul>
2003
+ </ul>
2004
+ </div>
1930
2005
  </div>
1931
- </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i3$4.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppBreadcrumb, selector: "[app-breadcrumb]" }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: IconFieldModule }, { kind: "component", type: i5$2.IconField, selector: "p-iconfield, p-iconField, p-icon-field", inputs: ["hostName", "iconPosition", "styleClass"] }, { kind: "ngmodule", type: InputIconModule }, { kind: "component", type: i6$1.InputIcon, selector: "p-inputicon, p-inputIcon", inputs: ["hostName", "styleClass"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i4$2.Ripple, selector: "[pRipple]" }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i8.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: OverlayBadgeModule }, { kind: "component", type: i9.OverlayBadge, selector: "p-overlayBadge, p-overlay-badge, p-overlaybadge", inputs: ["styleClass", "style", "badgeSize", "severity", "value", "badgeDisabled", "size"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i10.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }] });
2006
+
2007
+ <a class="mobile-logo" [routerLink]="['/']">
2008
+ <img [src]="mobileLogo()" [attr.alt]="mobileLogoConfig.alt" />
2009
+ </a>
2010
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: AppBreadcrumb, selector: "[app-breadcrumb]" }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: IconFieldModule }, { kind: "component", type: i4$3.IconField, selector: "p-iconfield, p-iconField, p-icon-field", inputs: ["hostName", "iconPosition", "styleClass"] }, { kind: "ngmodule", type: InputIconModule }, { kind: "component", type: i5$1.InputIcon, selector: "p-inputicon, p-inputIcon", inputs: ["hostName", "styleClass"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i4$2.Ripple, selector: "[pRipple]" }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i7.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: OverlayBadgeModule }, { kind: "component", type: i8.OverlayBadge, selector: "p-overlayBadge, p-overlay-badge, p-overlaybadge", inputs: ["styleClass", "style", "badgeSize", "severity", "value", "badgeDisabled", "size"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i9.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }] });
1932
2011
  }
1933
2012
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppTopbar, decorators: [{
1934
2013
  type: Component,
1935
2014
  args: [{
1936
2015
  selector: '[app-topbar]',
1937
- imports: [RouterModule, CommonModule, StyleClassModule, AppBreadcrumb, InputTextModule, ButtonModule, IconFieldModule, InputIconModule, RippleModule, BadgeModule, OverlayBadgeModule, AvatarModule],
2016
+ imports: [RouterModule, CommonModule, AppBreadcrumb, InputTextModule, ButtonModule, IconFieldModule, InputIconModule, RippleModule, BadgeModule, OverlayBadgeModule, AvatarModule],
1938
2017
  template: `<div class="layout-topbar">
1939
2018
  <button type="button" class="mobile-menu-button" aria-label="Toggle navigation menu" (click)="onMenuButtonClick()">
1940
2019
  <i class="pi pi-bars"></i>
1941
2020
  </button>
1942
2021
  <div class="topbar-left">
1943
- <button
1944
- type="button"
1945
- class="topbar-menu-toggle"
1946
- [class.active]="isSidebarPinned()"
1947
- [attr.aria-label]="isSidebarPinned() ? 'Collapse sidebar' : 'Expand sidebar'"
1948
- (click)="toggleSidebarPin()"
1949
- >
1950
- <i class="pi pi-bars"></i>
1951
- </button>
1952
- <a class="topbar-logo" [routerLink]="['/']">
1953
- <img [src]="desktopLogo()" [attr.alt]="mobileLogoConfig.alt" />
1954
- </a>
1955
- <span class="topbar-logo-separator"></span>
2022
+ <div class="topbar-sidebar-section">
2023
+ <button
2024
+ type="button"
2025
+ class="topbar-menu-toggle"
2026
+ [class.active]="isSidebarPinned()"
2027
+ [attr.aria-label]="isSidebarPinned() ? 'Collapse sidebar' : 'Expand sidebar'"
2028
+ (click)="toggleSidebarPin()"
2029
+ >
2030
+ <i class="pi pi-bars"></i>
2031
+ </button>
2032
+ <a class="topbar-logo" [routerLink]="['/']">
2033
+ <img [src]="desktopLogo()" [attr.alt]="mobileLogoConfig.alt" />
2034
+ </a>
2035
+ <span class="topbar-logo-separator"></span>
2036
+ </div>
2037
+ </div>
2038
+
2039
+ <div class="topbar-main">
1956
2040
  <div app-breadcrumb></div>
1957
2041
  @if (searchActive()) {
1958
2042
  <div class="flex items-center gap-2 ml-auto">
@@ -1965,14 +2049,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1965
2049
  </button>
1966
2050
  </div>
1967
2051
  }
1968
- </div>
1969
-
1970
- <a class="mobile-logo" [routerLink]="['/landing']">
1971
- <img [src]="mobileLogo()" [attr.alt]="mobileLogoConfig.alt" />
1972
- </a>
1973
-
1974
- <div class="topbar-right">
1975
- <ul class="topbar-menu">
2052
+ <div class="topbar-right">
2053
+ <ul class="topbar-menu">
1976
2054
  <li class="right-sidebar-item" [class.hidden]="searchActive()">
1977
2055
  <a class="right-sidebar-button" aria-label="Open search" (click)="openSearch()">
1978
2056
  <i class="pi pi-search"></i>
@@ -1987,56 +2065,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1987
2065
  <i [class]="isDarkTheme() ? 'pi pi-sun' : 'pi pi-moon'"></i>
1988
2066
  </a>
1989
2067
  </li>
1990
- <li class="right-sidebar-item static sm:relative z-50">
1991
- <a class="right-sidebar-button" aria-label="Notifications" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveActiveClass="animate-fadeout" leaveToClass="hidden" [hideOnOutsideClick]="true">
2068
+ <li class="right-sidebar-item static sm:relative z-50" #notificationsItem>
2069
+ <a class="right-sidebar-button" aria-label="Notifications" (click)="toggleDropdown('notifications', $event)">
1992
2070
  <span class="w-2 h-2 rounded-full bg-red-500 absolute top-2 right-2.5"></span>
1993
2071
  <i class="pi pi-bell"></i>
1994
2072
  </a>
1995
- <div
1996
- class="list-none m-0 rounded-2xl border border-surface fixed sm:absolute bg-surface-0 dark:bg-surface-900 overflow-hidden hidden origin-top w-[calc(100vw-2rem)] sm:w-88 mt-2 z-50 top-auto left-4 sm:left-auto sm:right-0 shadow-[0px_56px_16px_0px_rgba(0,0,0,0.00),0px_36px_14px_0px_rgba(0,0,0,0.01),0px_20px_12px_0px_rgba(0,0,0,0.02),0px_9px_9px_0px_rgba(0,0,0,0.03),0px_2px_5px_0px_rgba(0,0,0,0.04)]"
1997
- >
1998
- <div class="p-4 flex items-center justify-between border-b border-surface">
1999
- <span class="label-small text-surface-950 dark:text-surface-0">Notifications</span>
2000
- <button pRipple class="py-1 px-2 text-surface-950 dark:text-surface-0 label-x-small hover:bg-emphasis border border-surface rounded-lg shadow-[0px_1px_2px_0px_rgba(18,18,23,0.05)] transition-all">Mark all as read</button>
2001
- </div>
2002
- <div class="flex items-center border-b border-surface">
2003
- @for (item of notificationsBars(); track item.id; let i = $index) {
2004
- <button
2005
- [ngClass]="{ 'border-surface-950 dark:border-surface-0': selectedNotificationBar() === item.id, 'border-transparent': selectedNotificationBar() !== item.id }"
2006
- class="px-3.5 py-2 inline-flex items-center border-b gap-2"
2007
- (click)="selectedNotificationBar.set(item.id)"
2008
- >
2009
- <span [ngClass]="{ 'text-surface-950 dark:text-surface-0': selectedNotificationBar() === item.id }" class="label-small">{{ item.label }}</span>
2010
- <p-badge *ngIf="item?.badge" [value]="item.badge" severity="success" size="small" class="rounded-md!" />
2011
- </button>
2012
- }
2013
- </div>
2014
- <ul class="flex flex-col divide-y divide-(--surface-border) max-h-80 overflow-auto">
2015
- @for (item of selectedNotificationsBarData(); track item.name; let i = $index) {
2016
- <li>
2017
- <div class="flex items-center gap-3 px-4 sm:px-6 py-3.5 cursor-pointer hover:bg-emphasis transition-all">
2018
- <p-overlay-badge value="" severity="danger" class="inline-flex">
2019
- <p-avatar size="large">
2020
- <img [src]="item.image" [alt]="item.name" class="rounded-lg" />
2021
- </p-avatar>
2022
- </p-overlay-badge>
2023
- <div class="flex items-center gap-3">
2024
- <div class="flex flex-col">
2025
- <span class="label-small text-left text-surface-950 dark:text-surface-0">{{ item.name }}</span>
2026
- <span class="label-xsmall text-left line-clamp-1">{{ item.description }}</span>
2027
- <span class="label-xsmall text-left">{{ item.time }}</span>
2073
+ @if (activeDropdown() === 'notifications') {
2074
+ <div
2075
+ class="list-none m-0 rounded-2xl border border-surface fixed sm:absolute bg-surface-0 dark:bg-surface-900 overflow-hidden origin-top w-[calc(100vw-2rem)] sm:w-88 mt-2 z-50 top-auto left-4 sm:left-auto sm:right-0 shadow-flyout animate-scalein"
2076
+ >
2077
+ <div class="p-4 flex items-center justify-between border-b border-surface">
2078
+ <span class="label-small text-surface-950 dark:text-surface-0">Notifications</span>
2079
+ <button pRipple class="py-1 px-2 text-surface-950 dark:text-surface-0 label-x-small hover:bg-emphasis border border-surface rounded-lg shadow-subtle transition-all">Mark all as read</button>
2080
+ </div>
2081
+ <div class="flex items-center border-b border-surface">
2082
+ @for (item of notificationsBars(); track item.id; let i = $index) {
2083
+ <button
2084
+ [ngClass]="{ 'border-surface-950 dark:border-surface-0': selectedNotificationBar() === item.id, 'border-transparent': selectedNotificationBar() !== item.id }"
2085
+ class="px-3.5 py-2 inline-flex items-center border-b gap-2"
2086
+ (click)="selectedNotificationBar.set(item.id)"
2087
+ >
2088
+ <span [ngClass]="{ 'text-surface-950 dark:text-surface-0': selectedNotificationBar() === item.id }" class="label-small">{{ item.label }}</span>
2089
+ <p-badge *ngIf="item?.badge" [value]="item.badge" severity="success" size="small" class="rounded-md!" />
2090
+ </button>
2091
+ }
2092
+ </div>
2093
+ <ul class="flex flex-col divide-y divide-(--surface-border) max-h-80 overflow-auto">
2094
+ @for (item of selectedNotificationsBarData(); track item.name; let i = $index) {
2095
+ <li>
2096
+ <div class="flex items-center gap-3 px-4 sm:px-6 py-3.5 cursor-pointer hover:bg-emphasis transition-all">
2097
+ <p-overlay-badge value="" severity="danger" class="inline-flex">
2098
+ <p-avatar [label]="item.initials" size="large" styleClass="rounded-lg" />
2099
+ </p-overlay-badge>
2100
+ <div class="flex items-center gap-3">
2101
+ <div class="flex flex-col">
2102
+ <span class="label-small text-left text-surface-950 dark:text-surface-0">{{ item.name }}</span>
2103
+ <span class="label-xsmall text-left line-clamp-1">{{ item.description }}</span>
2104
+ <span class="label-xsmall text-left">{{ item.time }}</span>
2105
+ </div>
2106
+ <p-badge *ngIf="item.new" value="" severity="success" />
2028
2107
  </div>
2029
- <p-badge *ngIf="item.new" value="" severity="success" />
2030
2108
  </div>
2031
- </div>
2032
- <span *ngIf="i !== notifications().length - 1"></span>
2033
- </li>
2034
- }
2035
- </ul>
2036
- </div>
2109
+ <span *ngIf="i !== notifications().length - 1"></span>
2110
+ </li>
2111
+ }
2112
+ </ul>
2113
+ </div>
2114
+ }
2037
2115
  </li>
2038
- <li class="right-sidebar-item static sm:relative">
2039
- <a class="right-sidebar-button relative z-50" aria-label="Change language" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveActiveClass="animate-fadeout" leaveToClass="hidden" [hideOnOutsideClick]="true">
2116
+ <li class="right-sidebar-item static sm:relative" #languageItem>
2117
+ <a class="right-sidebar-button relative z-50" aria-label="Change language" (click)="toggleDropdown('language', $event)">
2040
2118
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
2041
2119
  <path d="m5 8 6 6"/>
2042
2120
  <path d="m4 14 6-6 2-3"/>
@@ -2046,112 +2124,119 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2046
2124
  <path d="M14 18h6"/>
2047
2125
  </svg>
2048
2126
  </a>
2049
- <div
2050
- class="list-none p-2 m-0 rounded-2xl border border-surface overflow-hidden fixed sm:absolute bg-surface-0 dark:bg-surface-900 hidden origin-top w-44 mt-2 right-4 sm:right-0 z-999 top-auto shadow-[0px_56px_16px_0px_rgba(0,0,0,0.00),0px_36px_14px_0px_rgba(0,0,0,0.01),0px_20px_12px_0px_rgba(0,0,0,0.02),0px_9px_9px_0px_rgba(0,0,0,0.03),0px_2px_5px_0px_rgba(0,0,0,0.04)]"
2051
- >
2052
- <ul class="flex flex-col gap-1">
2053
- @for (lang of languages(); track lang.code) {
2054
- <li>
2055
- <a
2056
- class="label-small dark:text-surface-400 flex gap-2.5 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer"
2057
- [class.text-surface-950]="selectedLanguage() === lang.code"
2058
- [class.dark:text-surface-0]="selectedLanguage() === lang.code"
2059
- [class.font-semibold]="selectedLanguage() === lang.code"
2060
- (click)="selectLanguage(lang.code)"
2061
- >
2062
- <span class="text-lg">{{ lang.flag }}</span>
2063
- <span>{{ lang.label }}</span>
2064
- </a>
2065
- </li>
2066
- }
2067
- </ul>
2068
- </div>
2127
+ @if (activeDropdown() === 'language') {
2128
+ <div
2129
+ class="list-none p-2 m-0 rounded-2xl border border-surface overflow-hidden fixed sm:absolute bg-surface-0 dark:bg-surface-900 origin-top w-44 mt-2 right-4 sm:right-0 z-999 top-auto shadow-flyout animate-scalein"
2130
+ >
2131
+ <ul class="flex flex-col gap-1">
2132
+ @for (lang of languages(); track lang.code) {
2133
+ <li>
2134
+ <a
2135
+ class="label-small dark:text-surface-400 flex gap-2.5 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer"
2136
+ [class.text-surface-950]="selectedLanguage() === lang.code"
2137
+ [class.dark:text-surface-0]="selectedLanguage() === lang.code"
2138
+ [class.font-semibold]="selectedLanguage() === lang.code"
2139
+ (click)="selectLanguage(lang.code)"
2140
+ >
2141
+ <span class="text-lg">{{ lang.flag }}</span>
2142
+ <span>{{ lang.label }}</span>
2143
+ </a>
2144
+ </li>
2145
+ }
2146
+ </ul>
2147
+ </div>
2148
+ }
2069
2149
  </li>
2070
2150
  <li class="profile-item static sm:relative" #profileItem>
2071
- <a class="right-sidebar-button relative z-50" aria-label="User profile menu" (click)="toggleProfileMenu($event)">
2151
+ <a class="right-sidebar-button relative z-50" aria-label="User profile menu" (click)="toggleDropdown('profile', $event)">
2072
2152
  <p-avatar icon="pi pi-user" styleClass="w-10! h-10!" />
2073
2153
  </a>
2074
- <div
2075
- #profilePanel
2076
- class="list-none p-2 m-0 rounded-2xl border border-surface overflow-hidden fixed sm:absolute bg-surface-0 dark:bg-surface-900 origin-top w-52 mt-2 right-4 sm:right-0 z-999 top-auto shadow-[0px_56px_16px_0px_rgba(0,0,0,0.00),0px_36px_14px_0px_rgba(0,0,0,0.01),0px_20px_12px_0px_rgba(0,0,0,0.02),0px_9px_9px_0px_rgba(0,0,0,0.03),0px_2px_5px_0px_rgba(0,0,0,0.04)]"
2077
- [class.hidden]="!profileMenuOpen()"
2078
- [class.animate-scalein]="profileMenuOpen()"
2079
- >
2080
- <ul class="flex flex-col gap-1">
2081
- <div class="mobile-profile-actions">
2154
+ @if (activeDropdown() === 'profile') {
2155
+ <div
2156
+ #profilePanel
2157
+ class="list-none p-2 m-0 rounded-2xl border border-surface overflow-hidden fixed sm:absolute bg-surface-0 dark:bg-surface-900 origin-top w-52 mt-2 right-4 sm:right-0 z-999 top-auto shadow-flyout animate-scalein"
2158
+ >
2159
+ <ul class="flex flex-col gap-1">
2160
+ <div class="mobile-profile-actions">
2161
+ <li>
2162
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown(); openSearch()">
2163
+ <i class="pi pi-search"></i>
2164
+ <span>Search</span>
2165
+ </a>
2166
+ </li>
2167
+ <li>
2168
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown(); toggleDarkMode()">
2169
+ <i [class]="isDarkTheme() ? 'pi pi-sun' : 'pi pi-moon'"></i>
2170
+ <span>{{ isDarkTheme() ? 'Light Mode' : 'Dark Mode' }}</span>
2171
+ </a>
2172
+ </li>
2173
+ <li>
2174
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer relative" (click)="closeDropdown()">
2175
+ <i class="pi pi-bell"></i>
2176
+ <span>Notifications</span>
2177
+ <span class="w-2 h-2 rounded-full bg-red-500 ml-auto"></span>
2178
+ </a>
2179
+ </li>
2180
+ <li class="border-b border-surface pb-1 mb-1">
2181
+ <span class="label-xsmall px-2.5 py-1 text-surface-400">Language</span>
2182
+ @for (lang of languages(); track lang.code) {
2183
+ <a
2184
+ class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer"
2185
+ [class.text-surface-950]="selectedLanguage() === lang.code"
2186
+ [class.dark:text-surface-0]="selectedLanguage() === lang.code"
2187
+ [class.font-semibold]="selectedLanguage() === lang.code"
2188
+ (click)="closeDropdown(); selectLanguage(lang.code)"
2189
+ >
2190
+ <span class="text-lg">{{ lang.flag }}</span>
2191
+ <span>{{ lang.label }}</span>
2192
+ @if (selectedLanguage() === lang.code) {
2193
+ <i class="pi pi-check ml-auto text-xs"></i>
2194
+ }
2195
+ </a>
2196
+ }
2197
+ </li>
2198
+ </div>
2082
2199
  <li>
2083
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false); openSearch()">
2084
- <i class="pi pi-search"></i>
2085
- <span>Search</span>
2200
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown()">
2201
+ <i class="pi pi-user"></i>
2202
+ <span>Profile</span>
2086
2203
  </a>
2087
2204
  </li>
2088
2205
  <li>
2089
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false); toggleDarkMode()">
2090
- <i [class]="isDarkTheme() ? 'pi pi-sun' : 'pi pi-moon'"></i>
2091
- <span>{{ isDarkTheme() ? 'Light Mode' : 'Dark Mode' }}</span>
2206
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown(); onConfigButtonClick()">
2207
+ <i class="pi pi-cog"></i>
2208
+ <span>Settings</span>
2092
2209
  </a>
2093
2210
  </li>
2094
2211
  <li>
2095
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer relative" (click)="profileMenuOpen.set(false)">
2096
- <i class="pi pi-bell"></i>
2097
- <span>Notifications</span>
2098
- <span class="w-2 h-2 rounded-full bg-red-500 ml-auto"></span>
2212
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown()">
2213
+ <i class="pi pi-calendar"></i>
2214
+ <span>Calendar</span>
2099
2215
  </a>
2100
2216
  </li>
2101
- <li class="border-b border-surface pb-1 mb-1">
2102
- <span class="label-xsmall px-2.5 py-1 text-surface-400">Language</span>
2103
- @for (lang of languages(); track lang.code) {
2104
- <a
2105
- class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer"
2106
- [class.text-surface-950]="selectedLanguage() === lang.code"
2107
- [class.dark:text-surface-0]="selectedLanguage() === lang.code"
2108
- [class.font-semibold]="selectedLanguage() === lang.code"
2109
- (click)="profileMenuOpen.set(false); selectLanguage(lang.code)"
2110
- >
2111
- <span class="text-lg">{{ lang.flag }}</span>
2112
- <span>{{ lang.label }}</span>
2113
- @if (selectedLanguage() === lang.code) {
2114
- <i class="pi pi-check ml-auto text-xs"></i>
2115
- }
2116
- </a>
2117
- }
2217
+ <li>
2218
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown()">
2219
+ <i class="pi pi-inbox"></i>
2220
+ <span>Inbox</span>
2221
+ </a>
2118
2222
  </li>
2119
- </div>
2120
- <li>
2121
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
2122
- <i class="pi pi-user"></i>
2123
- <span>Profile</span>
2124
- </a>
2125
- </li>
2126
- <li>
2127
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
2128
- <i class="pi pi-cog"></i>
2129
- <span>Settings</span>
2130
- </a>
2131
- </li>
2132
- <li>
2133
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
2134
- <i class="pi pi-calendar"></i>
2135
- <span>Calendar</span>
2136
- </a>
2137
- </li>
2138
- <li>
2139
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
2140
- <i class="pi pi-inbox"></i>
2141
- <span>Inbox</span>
2142
- </a>
2143
- </li>
2144
- <li class="border-t border-surface mt-1 pt-1">
2145
- <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="profileMenuOpen.set(false)">
2146
- <i class="pi pi-power-off"></i>
2147
- <span>Log out</span>
2148
- </a>
2149
- </li>
2150
- </ul>
2151
- </div>
2223
+ <li class="border-t border-surface mt-1 pt-1">
2224
+ <a class="label-small dark:text-surface-400 flex gap-2 py-2 px-2.5 rounded-lg items-center hover:bg-emphasis transition-colors duration-150 cursor-pointer" (click)="closeDropdown()">
2225
+ <i class="pi pi-power-off"></i>
2226
+ <span>Log out</span>
2227
+ </a>
2228
+ </li>
2229
+ </ul>
2230
+ </div>
2231
+ }
2152
2232
  </li>
2153
- </ul>
2233
+ </ul>
2234
+ </div>
2154
2235
  </div>
2236
+
2237
+ <a class="mobile-logo" [routerLink]="['/']">
2238
+ <img [src]="mobileLogo()" [attr.alt]="mobileLogoConfig.alt" />
2239
+ </a>
2155
2240
  </div>`
2156
2241
  }]
2157
2242
  }], propDecorators: { menuButton: [{
@@ -2160,18 +2245,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2160
2245
  }], searchInput: [{
2161
2246
  type: ViewChild,
2162
2247
  args: ['searchInput']
2248
+ }], notificationsItem: [{
2249
+ type: ViewChild,
2250
+ args: ['notificationsItem']
2251
+ }], languageItem: [{
2252
+ type: ViewChild,
2253
+ args: ['languageItem']
2163
2254
  }], profileItem: [{
2164
2255
  type: ViewChild,
2165
2256
  args: ['profileItem']
2166
- }], profilePanel: [{
2167
- type: ViewChild,
2168
- args: ['profilePanel']
2169
2257
  }], selectedNotificationBar: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedNotificationBar", required: false }] }, { type: i0.Output, args: ["selectedNotificationBarChange"] }], onDocumentClick: [{
2170
2258
  type: HostListener,
2171
2259
  args: ['document:click', ['$event']]
2172
2260
  }] } });
2173
2261
 
2174
- const BREAKPOINT = 992;
2262
+ const BREAKPOINT = 1024;
2175
2263
  class AppSidebar {
2176
2264
  layoutService = inject(LayoutService);
2177
2265
  router = inject(Router);
@@ -2378,22 +2466,28 @@ class AppSidebar {
2378
2466
  }
2379
2467
  }
2380
2468
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
2381
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: AppSidebar, isStandalone: true, selector: "[app-sidebar]", viewQueries: [{ propertyName: "menuContainer", first: true, predicate: ["menuContainer"], descendants: true }, { propertyName: "sidebarRef", first: true, predicate: ["sidebarRef"], descendants: true }], ngImport: i0, template: `<nav class="layout-sidebar" aria-label="Main navigation" (mouseleave)="onMouseLeave()">
2382
- <div #menuContainer class="layout-menu-container" (scroll)="onMenuScroll()">
2469
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: AppSidebar, isStandalone: true, selector: "[app-sidebar]", viewQueries: [{ propertyName: "menuContainer", first: true, predicate: ["menuContainer"], descendants: true }, { propertyName: "sidebarRef", first: true, predicate: ["sidebarRef"], descendants: true }], ngImport: i0, template: `<nav class="layout-sidebar" aria-label="Main navigation" (mouseleave)="onMouseLeave()">
2470
+ <div #menuContainer class="layout-menu-container min-h-0 grow" (scroll)="onMenuScroll()">
2383
2471
  <div app-menu></div>
2384
2472
  </div>
2473
+ @if (!isHorizontal()) {
2474
+ <ux-footer-main [copyrightOnly]="true" role="contentinfo" />
2475
+ }
2385
2476
  <div app-topbar *ngIf="isHorizontal()"></div>
2386
- </nav>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: AppMenu, selector: "[app-menu]" }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: AppTopbar, selector: "[app-topbar]", inputs: ["selectedNotificationBar"], outputs: ["selectedNotificationBarChange"] }] });
2477
+ </nav>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: AppMenu, selector: "[app-menu]" }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: AppTopbar, selector: "[app-topbar]", inputs: ["selectedNotificationBar"], outputs: ["selectedNotificationBarChange"] }, { kind: "component", type: FooterMainComponent, selector: "ux-footer-main", inputs: ["copyrightOnly"] }] });
2387
2478
  }
2388
2479
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppSidebar, decorators: [{
2389
2480
  type: Component,
2390
2481
  args: [{
2391
2482
  selector: '[app-sidebar]',
2392
- imports: [CommonModule, AppMenu, RouterModule, AppTopbar],
2483
+ imports: [CommonModule, AppMenu, RouterModule, AppTopbar, FooterMainComponent],
2393
2484
  template: `<nav class="layout-sidebar" aria-label="Main navigation" (mouseleave)="onMouseLeave()">
2394
- <div #menuContainer class="layout-menu-container" (scroll)="onMenuScroll()">
2485
+ <div #menuContainer class="layout-menu-container min-h-0 grow" (scroll)="onMenuScroll()">
2395
2486
  <div app-menu></div>
2396
2487
  </div>
2488
+ @if (!isHorizontal()) {
2489
+ <ux-footer-main [copyrightOnly]="true" role="contentinfo" />
2490
+ }
2397
2491
  <div app-topbar *ngIf="isHorizontal()"></div>
2398
2492
  </nav>`
2399
2493
  }]
@@ -2407,6 +2501,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2407
2501
 
2408
2502
  class AppLayout {
2409
2503
  layoutService = inject(LayoutService);
2504
+ elRef = inject(ElementRef);
2410
2505
  constructor() {
2411
2506
  effect(() => {
2412
2507
  const state = this.layoutService.layoutState();
@@ -2417,6 +2512,11 @@ class AppLayout {
2417
2512
  document.body.classList.remove('blocked-scroll');
2418
2513
  }
2419
2514
  });
2515
+ inject(Router).events.pipe(filter((e) => e instanceof NavigationEnd), takeUntilDestroyed()).subscribe(() => {
2516
+ const wrapper = this.elRef.nativeElement.querySelector('.layout-content-wrapper');
2517
+ if (wrapper)
2518
+ wrapper.scrollTop = 0;
2519
+ });
2420
2520
  }
2421
2521
  containerClass = computed(() => {
2422
2522
  const layoutConfig = this.layoutService.layoutConfig();
@@ -2449,21 +2549,21 @@ class AppLayout {
2449
2549
  <div app-breadcrumb></div>
2450
2550
  <router-outlet></router-outlet>
2451
2551
  </main>
2452
- <div app-footer></div>
2453
2552
  </div>
2553
+ <ux-footer-main class="footer-sticky" />
2454
2554
  </div>
2455
2555
  </div>
2456
2556
  <app-configurator />
2457
2557
  <div app-search></div>
2458
2558
  <div app-rightmenu></div>
2459
2559
  <div class="layout-mask"></div>
2460
- </div> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AppTopbar, selector: "[app-topbar]", inputs: ["selectedNotificationBar"], outputs: ["selectedNotificationBarChange"] }, { kind: "component", type: AppSidebar, selector: "[app-sidebar]" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator", inputs: ["simple", "location", "cardStyle"], outputs: ["cardStyleChange"] }, { kind: "component", type: AppBreadcrumb, selector: "[app-breadcrumb]" }, { kind: "component", type: AppFooter, selector: "[app-footer]" }, { kind: "component", type: AppSearch, selector: "[app-search]" }, { kind: "component", type: AppRightMenu, selector: "[app-rightmenu]" }] });
2560
+ </div> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AppTopbar, selector: "[app-topbar]", inputs: ["selectedNotificationBar"], outputs: ["selectedNotificationBarChange"] }, { kind: "component", type: AppSidebar, selector: "[app-sidebar]" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator", inputs: ["simple", "location", "cardStyle"], outputs: ["cardStyleChange"] }, { kind: "component", type: AppBreadcrumb, selector: "[app-breadcrumb]" }, { kind: "component", type: FooterMainComponent, selector: "ux-footer-main", inputs: ["copyrightOnly"] }, { kind: "component", type: AppSearch, selector: "[app-search]" }, { kind: "component", type: AppRightMenu, selector: "[app-rightmenu]" }] });
2461
2561
  }
2462
2562
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppLayout, decorators: [{
2463
2563
  type: Component,
2464
2564
  args: [{
2465
2565
  selector: 'app-layout',
2466
- imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppConfigurator, AppBreadcrumb, AppFooter, AppSearch, AppRightMenu],
2566
+ imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppConfigurator, AppBreadcrumb, FooterMainComponent, AppSearch, AppRightMenu],
2467
2567
  template: `<div class="layout-wrapper" [ngClass]="containerClass()">
2468
2568
  <div app-topbar></div>
2469
2569
  <div class="layout-body">
@@ -2474,8 +2574,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2474
2574
  <div app-breadcrumb></div>
2475
2575
  <router-outlet></router-outlet>
2476
2576
  </main>
2477
- <div app-footer></div>
2478
2577
  </div>
2578
+ <ux-footer-main class="footer-sticky" />
2479
2579
  </div>
2480
2580
  </div>
2481
2581
  <app-configurator />
@@ -2486,6 +2586,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2486
2586
  }]
2487
2587
  }], ctorParameters: () => [] });
2488
2588
 
2589
+ class AppFooter {
2590
+ layoutService = inject(LayoutService);
2591
+ copyrightYear = new Date().getFullYear();
2592
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
2593
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: AppFooter, isStandalone: true, selector: "[app-footer]", ngImport: i0, template: `
2594
+ @if (layoutService.isHorizontal()) {
2595
+ <footer class="layout-footer">
2596
+ <span class="footer-copyright">&#169; UNOPS {{ copyrightYear }}</span>
2597
+ </footer>
2598
+ }
2599
+ `, isInline: true });
2600
+ }
2601
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AppFooter, decorators: [{
2602
+ type: Component,
2603
+ args: [{
2604
+ selector: '[app-footer]',
2605
+ template: `
2606
+ @if (layoutService.isHorizontal()) {
2607
+ <footer class="layout-footer">
2608
+ <span class="footer-copyright">&#169; UNOPS {{ copyrightYear }}</span>
2609
+ </footer>
2610
+ }
2611
+ `
2612
+ }]
2613
+ }] });
2614
+
2489
2615
  class AuthLayout {
2490
2616
  layoutService = inject(LayoutService);
2491
2617
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AuthLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -2571,7 +2697,7 @@ class AiCardBgComponent {
2571
2697
  />
2572
2698
  </svg>
2573
2699
  <ng-content />
2574
- `, isInline: true, styles: [":host{--ux-ai-bg-from: #cce5ff;--ux-ai-bg-to:rgb(255, 236, 254);--ux-ai-fg-from:rgb(188, 214, 255);--ux-ai-fg-to:rgb(246, 216, 255);position:relative;overflow:hidden;isolation:isolate;contain:paint;animation:ux-ai-bg-move 5s ease-in-out infinite;will-change:background-color}:host-context([class*=\"app-dark\"]){--ux-ai-bg-from:rgb(0, 32, 69);--ux-ai-bg-to:rgb(47, 18, 116);--ux-ai-fg-from:rgb(0, 20, 47);--ux-ai-fg-to:rgb(76, 1, 71)}:host>:not(svg){position:relative;z-index:1}:host svg.ux-ai-card-bg__svg{position:absolute;inset:0;z-index:0;height:100%;width:100%;max-height:100%;overflow:hidden;pointer-events:none}.ux-ai-fg{will-change:fill;animation:ux-ai-fg-move 6s ease-in-out infinite}.ux-ai-fg--1{animation-delay:0s}.ux-ai-fg--2{animation-delay:2s}.ux-ai-fg--3{animation-delay:4s}@keyframes ux-ai-bg-move{0%,to{background-color:var(--ux-ai-bg-from)}50%{background-color:var(--ux-ai-bg-to)}}@keyframes ux-ai-fg-move{0%,to{fill:var(--ux-ai-fg-from)}50%{fill:var(--ux-ai-fg-to)}}@media(prefers-reduced-motion:reduce){:host{animation:none;background-color:var(--ux-ai-bg-from)}.ux-ai-fg{animation:none;fill:var(--ux-ai-fg-from)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2700
+ `, isInline: true, styles: [":host{--ux-ai-bg-from: var(--color-ai-50);--ux-ai-bg-to: var(--color-cherry-50);--ux-ai-fg-from: var(--color-ai-200);--ux-ai-fg-to: var(--color-cherry-100);position:relative;overflow:hidden;isolation:isolate;contain:paint;animation:ux-ai-bg-move 5s ease-in-out infinite;will-change:background-color}:host-context([class*=\"app-dark\"]){--ux-ai-bg-from: var(--color-ai-950);--ux-ai-bg-to: var(--color-cherry-950);--ux-ai-fg-from: var(--color-ai-900);--ux-ai-fg-to: var(--color-cherry-900)}:host>:not(svg){position:relative;z-index:1}:host svg.ux-ai-card-bg__svg{position:absolute;inset:0;z-index:0;height:100%;width:100%;max-height:100%;overflow:hidden;pointer-events:none}.ux-ai-fg{will-change:fill;animation:ux-ai-fg-move 6s ease-in-out infinite}.ux-ai-fg--1{animation-delay:0s}.ux-ai-fg--2{animation-delay:2s}.ux-ai-fg--3{animation-delay:4s}@keyframes ux-ai-bg-move{0%,to{background-color:var(--ux-ai-bg-from)}50%{background-color:var(--ux-ai-bg-to)}}@keyframes ux-ai-fg-move{0%,to{fill:var(--ux-ai-fg-from)}50%{fill:var(--ux-ai-fg-to)}}@media(prefers-reduced-motion:reduce){:host{animation:none;background-color:var(--ux-ai-bg-from)}.ux-ai-fg{animation:none;fill:var(--ux-ai-fg-from)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2575
2701
  }
2576
2702
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AiCardBgComponent, decorators: [{
2577
2703
  type: Component,
@@ -2620,7 +2746,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2620
2746
  />
2621
2747
  </svg>
2622
2748
  <ng-content />
2623
- `, styles: [":host{--ux-ai-bg-from: #cce5ff;--ux-ai-bg-to:rgb(255, 236, 254);--ux-ai-fg-from:rgb(188, 214, 255);--ux-ai-fg-to:rgb(246, 216, 255);position:relative;overflow:hidden;isolation:isolate;contain:paint;animation:ux-ai-bg-move 5s ease-in-out infinite;will-change:background-color}:host-context([class*=\"app-dark\"]){--ux-ai-bg-from:rgb(0, 32, 69);--ux-ai-bg-to:rgb(47, 18, 116);--ux-ai-fg-from:rgb(0, 20, 47);--ux-ai-fg-to:rgb(76, 1, 71)}:host>:not(svg){position:relative;z-index:1}:host svg.ux-ai-card-bg__svg{position:absolute;inset:0;z-index:0;height:100%;width:100%;max-height:100%;overflow:hidden;pointer-events:none}.ux-ai-fg{will-change:fill;animation:ux-ai-fg-move 6s ease-in-out infinite}.ux-ai-fg--1{animation-delay:0s}.ux-ai-fg--2{animation-delay:2s}.ux-ai-fg--3{animation-delay:4s}@keyframes ux-ai-bg-move{0%,to{background-color:var(--ux-ai-bg-from)}50%{background-color:var(--ux-ai-bg-to)}}@keyframes ux-ai-fg-move{0%,to{fill:var(--ux-ai-fg-from)}50%{fill:var(--ux-ai-fg-to)}}@media(prefers-reduced-motion:reduce){:host{animation:none;background-color:var(--ux-ai-bg-from)}.ux-ai-fg{animation:none;fill:var(--ux-ai-fg-from)}}\n"] }]
2749
+ `, styles: [":host{--ux-ai-bg-from: var(--color-ai-50);--ux-ai-bg-to: var(--color-cherry-50);--ux-ai-fg-from: var(--color-ai-200);--ux-ai-fg-to: var(--color-cherry-100);position:relative;overflow:hidden;isolation:isolate;contain:paint;animation:ux-ai-bg-move 5s ease-in-out infinite;will-change:background-color}:host-context([class*=\"app-dark\"]){--ux-ai-bg-from: var(--color-ai-950);--ux-ai-bg-to: var(--color-cherry-950);--ux-ai-fg-from: var(--color-ai-900);--ux-ai-fg-to: var(--color-cherry-900)}:host>:not(svg){position:relative;z-index:1}:host svg.ux-ai-card-bg__svg{position:absolute;inset:0;z-index:0;height:100%;width:100%;max-height:100%;overflow:hidden;pointer-events:none}.ux-ai-fg{will-change:fill;animation:ux-ai-fg-move 6s ease-in-out infinite}.ux-ai-fg--1{animation-delay:0s}.ux-ai-fg--2{animation-delay:2s}.ux-ai-fg--3{animation-delay:4s}@keyframes ux-ai-bg-move{0%,to{background-color:var(--ux-ai-bg-from)}50%{background-color:var(--ux-ai-bg-to)}}@keyframes ux-ai-fg-move{0%,to{fill:var(--ux-ai-fg-from)}50%{fill:var(--ux-ai-fg-to)}}@media(prefers-reduced-motion:reduce){:host{animation:none;background-color:var(--ux-ai-bg-from)}.ux-ai-fg{animation:none;fill:var(--ux-ai-fg-from)}}\n"] }]
2624
2750
  }] });
2625
2751
 
2626
2752
  class AiInsightsCardComponent {
@@ -2658,27 +2784,25 @@ class AiInsightsCardComponent {
2658
2784
  return Math.max(1, Math.floor(available / insightCardHeight));
2659
2785
  }
2660
2786
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AiInsightsCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2661
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: AiInsightsCardComponent, isStandalone: true, selector: "ux-ai-insights-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, insights: { classPropertyName: "insights", publicName: "insights", isSignal: true, isRequired: true, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, host: { properties: { "class.h-[calc(100dvh-12rem)": "expanded()" }, classAttribute: "ux-ai-insights-card block border border-[#e0e7ff] dark:border-[#2d3a5c] rounded-2xl shadow-sm overflow-hidden transition-all duration-300 flex flex-col max-h-[calc(100dvh-12rem)]" }, ngImport: i0, template: `
2662
- <ux-ai-card-bg class="flex flex-col flex-1 min-h-0 p-4">
2663
- <div class="motion-safe:animate-enter-liquid [animation-delay:80ms] flex flex-col flex-1 min-h-0">
2664
- <div class="flex items-center justify-between cursor-pointer shrink-0" (click)="expanded.set(!expanded())">
2787
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: AiInsightsCardComponent, isStandalone: true, selector: "ux-ai-insights-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, insights: { classPropertyName: "insights", publicName: "insights", isSignal: true, isRequired: true, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, host: { properties: { "class.ux-ai-expanded": "expanded()" }, classAttribute: "ux-ai-insights-card block border border-blue-100 dark:border-midnight-400 rounded-2xl shadow-sm overflow-hidden transition-all duration-300 flex flex-col" }, ngImport: i0, template: `
2788
+ <ux-ai-card-bg class="flex flex-col flex-1 p-4">
2789
+ <div class="motion-safe:animate-enter-liquid [animation-delay:80ms] flex flex-col flex-1">
2790
+ <div class="flex items-center justify-between cursor-pointer shrink-0 pr-2" (click)="expanded.set(!expanded())">
2665
2791
  <div class="flex items-center gap-3">
2666
2792
  <div class="w-[34px] h-[34px] rounded-[10px] flex items-center justify-center shrink-0">
2667
- <i class="pi pi-sparkles text-blue-800 dark:text-blue-300"></i>
2793
+ <i class="pi pi-sparkles text-ai-600 dark:text-ai-300"></i>
2668
2794
  </div>
2669
2795
  <div class="flex flex-col">
2670
2796
  <h4 class="title-h4 text-left text-deepsea-500 dark:text-surface-0">{{ title() }}</h4>
2671
2797
  <span class="text-midnight-700 dark:text-surface-100 text-sm font-medium leading-tight">{{ insights().length }} insights available for your review</span>
2672
2798
  </div>
2673
2799
  </div>
2674
- <button class="w-[30px] h-[30px] rounded-full bg-white/85 dark:bg-transparent border border-white dark:border-surface-300 shadow-sm flex items-center justify-center cursor-pointer hover:bg-white dark:hover:bg-white/10 transition-colors">
2675
- <i class="pi text-xs text-darkblue-500 dark:text-surface-0" [ngClass]="expanded() ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
2676
- </button>
2800
+ <i class="pi text-sm text-darkblue-500 dark:text-surface-0" [ngClass]="expanded() ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
2677
2801
  </div>
2678
2802
 
2679
2803
  <div class="expand-body" [class.expand-body--open]="expanded()">
2680
2804
  <div class="expand-body__inner">
2681
- <div class="flex flex-col gap-4 mt-4 flex-1 min-h-0">
2805
+ <div class="flex flex-col gap-4 mt-4">
2682
2806
  <div class="bg-white/60 dark:bg-surface-800/60 border border-white dark:border-surface-700 rounded-[14px] shadow-sm flex items-center gap-4 px-4 py-2.5 shrink-0">
2683
2807
  <i class="pi pi-search text-surface-500 dark:text-surface-300 text-sm"></i>
2684
2808
  <input
@@ -2690,14 +2814,14 @@ class AiInsightsCardComponent {
2690
2814
  />
2691
2815
  </div>
2692
2816
 
2693
- <div class="flex flex-col gap-3 flex-1 min-h-0 overflow-y-auto overscroll-y-contain pr-0.5">
2817
+ <div class="flex flex-col gap-3">
2694
2818
  @for (insight of paginatedInsights(); track insight.id) {
2695
2819
  <div class="bg-white/70 dark:bg-surface-800/70 border border-white/50 dark:border-surface-700/50 rounded-[14px] shadow-sm p-4 flex gap-3 items-start shrink-0">
2696
2820
  <i class="pi mt-0.5" [ngClass]="[insight.icon, insight.iconColor]"></i>
2697
2821
  <div class="flex flex-col gap-2 flex-1 min-w-0">
2698
2822
  <div class="flex flex-col gap-1">
2699
2823
  <span class="text-midnight-500 dark:text-surface-0 text-sm font-bold leading-[21px]">{{ insight.title }}</span>
2700
- <p class="text-[#2b638b] dark:text-surface-300 text-sm leading-normal">{{ insight.description }}</p>
2824
+ <p class="text-midnight-400 dark:text-surface-300 text-sm leading-normal">{{ insight.description }}</p>
2701
2825
  </div>
2702
2826
  <button
2703
2827
  class="flex items-center gap-1.5 text-darkblue-500 dark:text-primary-400 text-sm font-semibold cursor-pointer hover:underline bg-transparent border-none p-0 w-fit"
@@ -2718,8 +2842,8 @@ class AiInsightsCardComponent {
2718
2842
  [first]="first()"
2719
2843
  (onPageChange)="page.set($event.page ?? 0)"
2720
2844
  [pageLinkSize]="3"
2721
- styleClass="w-full border-none! bg-transparent!"
2722
- [pt]="{ root: { class: 'bg-transparent! relative! w-full! justify-center!' } }"
2845
+ styleClass="w-full"
2846
+ [pt]="{ root: { class: 'justify-center' } }"
2723
2847
  />
2724
2848
  </div>
2725
2849
  </div>
@@ -2727,34 +2851,32 @@ class AiInsightsCardComponent {
2727
2851
  </div>
2728
2852
  </div>
2729
2853
  </ux-ai-card-bg>
2730
- `, isInline: true, styles: [":host{display:flex}\n"], dependencies: [{ kind: "component", type: AiCardBgComponent, selector: "ux-ai-card-bg" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i5.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i2$1.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first", "appendTo"], outputs: ["onPageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2854
+ `, isInline: true, styles: [":host{display:flex}\n"], dependencies: [{ kind: "component", type: AiCardBgComponent, selector: "ux-ai-card-bg" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i2$2.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first", "appendTo"], outputs: ["onPageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2731
2855
  }
2732
2856
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AiInsightsCardComponent, decorators: [{
2733
2857
  type: Component,
2734
2858
  args: [{ selector: 'ux-ai-insights-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AiCardBgComponent, FormsModule, NgClass, PaginatorModule], host: {
2735
- class: 'ux-ai-insights-card block border border-[#e0e7ff] dark:border-[#2d3a5c] rounded-2xl shadow-sm overflow-hidden transition-all duration-300 flex flex-col max-h-[calc(100dvh-12rem)]',
2736
- '[class.h-[calc(100dvh-12rem)]]': 'expanded()'
2859
+ class: 'ux-ai-insights-card block border border-blue-100 dark:border-midnight-400 rounded-2xl shadow-sm overflow-hidden transition-all duration-300 flex flex-col',
2860
+ '[class.ux-ai-expanded]': 'expanded()'
2737
2861
  }, template: `
2738
- <ux-ai-card-bg class="flex flex-col flex-1 min-h-0 p-4">
2739
- <div class="motion-safe:animate-enter-liquid [animation-delay:80ms] flex flex-col flex-1 min-h-0">
2740
- <div class="flex items-center justify-between cursor-pointer shrink-0" (click)="expanded.set(!expanded())">
2862
+ <ux-ai-card-bg class="flex flex-col flex-1 p-4">
2863
+ <div class="motion-safe:animate-enter-liquid [animation-delay:80ms] flex flex-col flex-1">
2864
+ <div class="flex items-center justify-between cursor-pointer shrink-0 pr-2" (click)="expanded.set(!expanded())">
2741
2865
  <div class="flex items-center gap-3">
2742
2866
  <div class="w-[34px] h-[34px] rounded-[10px] flex items-center justify-center shrink-0">
2743
- <i class="pi pi-sparkles text-blue-800 dark:text-blue-300"></i>
2867
+ <i class="pi pi-sparkles text-ai-600 dark:text-ai-300"></i>
2744
2868
  </div>
2745
2869
  <div class="flex flex-col">
2746
2870
  <h4 class="title-h4 text-left text-deepsea-500 dark:text-surface-0">{{ title() }}</h4>
2747
2871
  <span class="text-midnight-700 dark:text-surface-100 text-sm font-medium leading-tight">{{ insights().length }} insights available for your review</span>
2748
2872
  </div>
2749
2873
  </div>
2750
- <button class="w-[30px] h-[30px] rounded-full bg-white/85 dark:bg-transparent border border-white dark:border-surface-300 shadow-sm flex items-center justify-center cursor-pointer hover:bg-white dark:hover:bg-white/10 transition-colors">
2751
- <i class="pi text-xs text-darkblue-500 dark:text-surface-0" [ngClass]="expanded() ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
2752
- </button>
2874
+ <i class="pi text-sm text-darkblue-500 dark:text-surface-0" [ngClass]="expanded() ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
2753
2875
  </div>
2754
2876
 
2755
2877
  <div class="expand-body" [class.expand-body--open]="expanded()">
2756
2878
  <div class="expand-body__inner">
2757
- <div class="flex flex-col gap-4 mt-4 flex-1 min-h-0">
2879
+ <div class="flex flex-col gap-4 mt-4">
2758
2880
  <div class="bg-white/60 dark:bg-surface-800/60 border border-white dark:border-surface-700 rounded-[14px] shadow-sm flex items-center gap-4 px-4 py-2.5 shrink-0">
2759
2881
  <i class="pi pi-search text-surface-500 dark:text-surface-300 text-sm"></i>
2760
2882
  <input
@@ -2766,14 +2888,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2766
2888
  />
2767
2889
  </div>
2768
2890
 
2769
- <div class="flex flex-col gap-3 flex-1 min-h-0 overflow-y-auto overscroll-y-contain pr-0.5">
2891
+ <div class="flex flex-col gap-3">
2770
2892
  @for (insight of paginatedInsights(); track insight.id) {
2771
2893
  <div class="bg-white/70 dark:bg-surface-800/70 border border-white/50 dark:border-surface-700/50 rounded-[14px] shadow-sm p-4 flex gap-3 items-start shrink-0">
2772
2894
  <i class="pi mt-0.5" [ngClass]="[insight.icon, insight.iconColor]"></i>
2773
2895
  <div class="flex flex-col gap-2 flex-1 min-w-0">
2774
2896
  <div class="flex flex-col gap-1">
2775
2897
  <span class="text-midnight-500 dark:text-surface-0 text-sm font-bold leading-[21px]">{{ insight.title }}</span>
2776
- <p class="text-[#2b638b] dark:text-surface-300 text-sm leading-normal">{{ insight.description }}</p>
2898
+ <p class="text-midnight-400 dark:text-surface-300 text-sm leading-normal">{{ insight.description }}</p>
2777
2899
  </div>
2778
2900
  <button
2779
2901
  class="flex items-center gap-1.5 text-darkblue-500 dark:text-primary-400 text-sm font-semibold cursor-pointer hover:underline bg-transparent border-none p-0 w-fit"
@@ -2794,8 +2916,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2794
2916
  [first]="first()"
2795
2917
  (onPageChange)="page.set($event.page ?? 0)"
2796
2918
  [pageLinkSize]="3"
2797
- styleClass="w-full border-none! bg-transparent!"
2798
- [pt]="{ root: { class: 'bg-transparent! relative! w-full! justify-center!' } }"
2919
+ styleClass="w-full"
2920
+ [pt]="{ root: { class: 'justify-center' } }"
2799
2921
  />
2800
2922
  </div>
2801
2923
  </div>
@@ -2806,6 +2928,153 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2806
2928
  `, styles: [":host{display:flex}\n"] }]
2807
2929
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], insights: [{ type: i0.Input, args: [{ isSignal: true, alias: "insights", required: true }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
2808
2930
 
2931
+ class CompletionStepsComponent {
2932
+ title = input('Completion Steps', ...(ngDevMode ? [{ debugName: "title" }] : []));
2933
+ steps = input([], ...(ngDevMode ? [{ debugName: "steps" }] : []));
2934
+ mandatory = input({ filled: 0, total: 0 }, ...(ngDevMode ? [{ debugName: "mandatory" }] : []));
2935
+ optional = input({ filled: 0, total: 0 }, ...(ngDevMode ? [{ debugName: "optional" }] : []));
2936
+ totalRecords = input(0, ...(ngDevMode ? [{ debugName: "totalRecords" }] : []));
2937
+ interactive = input(false, ...(ngDevMode ? [{ debugName: "interactive" }] : []));
2938
+ emptyTitle = input('No progress tracked yet', ...(ngDevMode ? [{ debugName: "emptyTitle" }] : []));
2939
+ emptyDescription = input('Progress is calculated automatically as you fill in the opportunity sections. Each dot represents a required or optional field — mandatory fields (bordered red) must be completed before submission.', ...(ngDevMode ? [{ debugName: "emptyDescription" }] : []));
2940
+ stepClick = output();
2941
+ dotStyles = {
2942
+ mandatoryFilled: { bg: 'bg-green-200 dark:bg-green-700', text: 'text-green-800 dark:text-green-50', icon: 'pi', iconClass: 'pi-check' },
2943
+ optionalFilled: { bg: 'bg-blue-200 dark:bg-blue-700', text: 'text-blue-800 dark:text-blue-50', icon: 'material', iconClass: 'info_i' },
2944
+ mandatoryMissing: { bg: 'bg-transparent border-2 border-red-400 dark:border-red-500', text: 'text-red-500 dark:text-red-400', icon: 'pi', iconClass: 'pi-plus' },
2945
+ optionalMissing: { bg: 'bg-transparent border-2 border-surface-300 dark:border-surface-600', text: 'text-surface-500 dark:text-surface-400', icon: 'material', iconClass: 'info_i' }
2946
+ };
2947
+ filledTotal = computed(() => this.mandatory().filled + this.optional().filled, ...(ngDevMode ? [{ debugName: "filledTotal" }] : []));
2948
+ empty = computed(() => this.filledTotal() === 0, ...(ngDevMode ? [{ debugName: "empty" }] : []));
2949
+ legendMandatoryBg = computed(() => this.mandatory().filled > 0 ? this.dotStyles.mandatoryFilled.bg : this.dotStyles.mandatoryMissing.bg, ...(ngDevMode ? [{ debugName: "legendMandatoryBg" }] : []));
2950
+ legendOptionalBg = computed(() => this.optional().filled > 0 ? this.dotStyles.optionalFilled.bg : this.dotStyles.optionalMissing.bg, ...(ngDevMode ? [{ debugName: "legendOptionalBg" }] : []));
2951
+ getDotStyle(step) {
2952
+ if (step.filled)
2953
+ return step.type === 'mandatory' ? this.dotStyles.mandatoryFilled : this.dotStyles.optionalFilled;
2954
+ return step.type === 'mandatory' ? this.dotStyles.mandatoryMissing : this.dotStyles.optionalMissing;
2955
+ }
2956
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: CompletionStepsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2957
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: CompletionStepsComponent, isStandalone: true, selector: "ux-completion-steps", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: false, transformFunction: null }, mandatory: { classPropertyName: "mandatory", publicName: "mandatory", isSignal: true, isRequired: false, transformFunction: null }, optional: { classPropertyName: "optional", publicName: "optional", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, emptyTitle: { classPropertyName: "emptyTitle", publicName: "emptyTitle", isSignal: true, isRequired: false, transformFunction: null }, emptyDescription: { classPropertyName: "emptyDescription", publicName: "emptyDescription", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stepClick: "stepClick" }, host: { classAttribute: "block" }, ngImport: i0, template: `
2958
+ <div class="card flex flex-col gap-4">
2959
+ <div class="flex items-center justify-between">
2960
+ <div class="flex items-center gap-3">
2961
+ <div class="flex flex-col">
2962
+ <span class="text-xs font-semibold text-surface-600 dark:text-surface-300 uppercase tracking-wide">{{ title() }}</span>
2963
+ </div>
2964
+ </div>
2965
+ <span class="text-2xl font-bold" [class]="filledTotal() > 0 ? 'text-surface-900 dark:text-surface-0' : 'text-surface-500 dark:text-surface-400'">{{ filledTotal() }}/{{ totalRecords() }}</span>
2966
+ </div>
2967
+
2968
+ <div class="flex items-center gap-1 flex-wrap">
2969
+ @for (step of steps(); track $index) {
2970
+ <span class="inline-flex items-center justify-center w-6 h-6 rounded-full"
2971
+ [class.cursor-pointer]="interactive()"
2972
+ [class]="getDotStyle(step).bg"
2973
+ [pTooltip]="step.name + (step.filled ? '' : ' (missing)')" tooltipPosition="top"
2974
+ (click)="interactive() && stepClick.emit($index)">
2975
+ @if (getDotStyle(step).icon === 'pi') {
2976
+ <i class="pi text-[3px]" [class]="getDotStyle(step).iconClass + ' ' + getDotStyle(step).text"></i>
2977
+ } @else if (getDotStyle(step).icon === 'material') {
2978
+ <span class="material-symbols-outlined leading-none" style="font-size:20px;transform:scale(0.9)" [class]="getDotStyle(step).text">{{ getDotStyle(step).iconClass }}</span>
2979
+ } @else {
2980
+ <span class="text-sm font-black leading-none" [class]="getDotStyle(step).text">!</span>
2981
+ }
2982
+ </span>
2983
+ }
2984
+ </div>
2985
+
2986
+ <div class="flex flex-wrap items-center gap-x-6 gap-y-2 mt-1">
2987
+ <div class="flex items-center gap-2">
2988
+ <span class="inline-block w-4 h-4 rounded-full shrink-0" [class]="legendMandatoryBg()"></span>
2989
+ <span class="text-sm text-surface-600 dark:text-surface-300">Mandatory:</span>
2990
+ <span class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ mandatory().filled }}/{{ mandatory().total }}</span>
2991
+ </div>
2992
+ <div class="flex items-center gap-2">
2993
+ <span class="inline-block w-4 h-4 rounded-full shrink-0" [class]="legendOptionalBg()"></span>
2994
+ <span class="text-sm text-surface-600 dark:text-surface-300">Optional:</span>
2995
+ <span class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ optional().filled }}/{{ optional().total }}</span>
2996
+ </div>
2997
+ <div class="flex items-center gap-2">
2998
+ <span class="text-sm text-surface-600 dark:text-surface-300">Total:</span>
2999
+ <span class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ totalRecords() }}</span>
3000
+ </div>
3001
+ </div>
3002
+
3003
+ @if (empty()) {
3004
+ <div class="empty-state">
3005
+ <i class="pi pi-chart-bar text-3xl text-surface-500 dark:text-surface-400"></i>
3006
+ <span class="empty-state-title">{{ emptyTitle() }}</span>
3007
+ <span class="empty-state-desc">{{ emptyDescription() }}</span>
3008
+ </div>
3009
+ }
3010
+ </div>
3011
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i3$3.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3012
+ }
3013
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: CompletionStepsComponent, decorators: [{
3014
+ type: Component,
3015
+ args: [{
3016
+ selector: 'ux-completion-steps',
3017
+ changeDetection: ChangeDetectionStrategy.OnPush,
3018
+ imports: [TooltipModule],
3019
+ host: { class: 'block' },
3020
+ template: `
3021
+ <div class="card flex flex-col gap-4">
3022
+ <div class="flex items-center justify-between">
3023
+ <div class="flex items-center gap-3">
3024
+ <div class="flex flex-col">
3025
+ <span class="text-xs font-semibold text-surface-600 dark:text-surface-300 uppercase tracking-wide">{{ title() }}</span>
3026
+ </div>
3027
+ </div>
3028
+ <span class="text-2xl font-bold" [class]="filledTotal() > 0 ? 'text-surface-900 dark:text-surface-0' : 'text-surface-500 dark:text-surface-400'">{{ filledTotal() }}/{{ totalRecords() }}</span>
3029
+ </div>
3030
+
3031
+ <div class="flex items-center gap-1 flex-wrap">
3032
+ @for (step of steps(); track $index) {
3033
+ <span class="inline-flex items-center justify-center w-6 h-6 rounded-full"
3034
+ [class.cursor-pointer]="interactive()"
3035
+ [class]="getDotStyle(step).bg"
3036
+ [pTooltip]="step.name + (step.filled ? '' : ' (missing)')" tooltipPosition="top"
3037
+ (click)="interactive() && stepClick.emit($index)">
3038
+ @if (getDotStyle(step).icon === 'pi') {
3039
+ <i class="pi text-[3px]" [class]="getDotStyle(step).iconClass + ' ' + getDotStyle(step).text"></i>
3040
+ } @else if (getDotStyle(step).icon === 'material') {
3041
+ <span class="material-symbols-outlined leading-none" style="font-size:20px;transform:scale(0.9)" [class]="getDotStyle(step).text">{{ getDotStyle(step).iconClass }}</span>
3042
+ } @else {
3043
+ <span class="text-sm font-black leading-none" [class]="getDotStyle(step).text">!</span>
3044
+ }
3045
+ </span>
3046
+ }
3047
+ </div>
3048
+
3049
+ <div class="flex flex-wrap items-center gap-x-6 gap-y-2 mt-1">
3050
+ <div class="flex items-center gap-2">
3051
+ <span class="inline-block w-4 h-4 rounded-full shrink-0" [class]="legendMandatoryBg()"></span>
3052
+ <span class="text-sm text-surface-600 dark:text-surface-300">Mandatory:</span>
3053
+ <span class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ mandatory().filled }}/{{ mandatory().total }}</span>
3054
+ </div>
3055
+ <div class="flex items-center gap-2">
3056
+ <span class="inline-block w-4 h-4 rounded-full shrink-0" [class]="legendOptionalBg()"></span>
3057
+ <span class="text-sm text-surface-600 dark:text-surface-300">Optional:</span>
3058
+ <span class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ optional().filled }}/{{ optional().total }}</span>
3059
+ </div>
3060
+ <div class="flex items-center gap-2">
3061
+ <span class="text-sm text-surface-600 dark:text-surface-300">Total:</span>
3062
+ <span class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ totalRecords() }}</span>
3063
+ </div>
3064
+ </div>
3065
+
3066
+ @if (empty()) {
3067
+ <div class="empty-state">
3068
+ <i class="pi pi-chart-bar text-3xl text-surface-500 dark:text-surface-400"></i>
3069
+ <span class="empty-state-title">{{ emptyTitle() }}</span>
3070
+ <span class="empty-state-desc">{{ emptyDescription() }}</span>
3071
+ </div>
3072
+ }
3073
+ </div>
3074
+ `
3075
+ }]
3076
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: false }] }], mandatory: [{ type: i0.Input, args: [{ isSignal: true, alias: "mandatory", required: false }] }], optional: [{ type: i0.Input, args: [{ isSignal: true, alias: "optional", required: false }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], emptyTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTitle", required: false }] }], emptyDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyDescription", required: false }] }], stepClick: [{ type: i0.Output, args: ["stepClick"] }] } });
3077
+
2809
3078
  /**
2810
3079
  * Structural directive that marks a template as content for a specific tab
2811
3080
  * inside `<ux-detail-layout>`.
@@ -2843,12 +3112,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2843
3112
  * <ng-template uxDetailTab="scope">...tab 2...</ng-template>
2844
3113
  *
2845
3114
  * <ng-container ux-detail-sidebar>
3115
+ * <!-- Use ng-container so children become direct children of the
3116
+ * library's flex container and inherit the gap spacing. -->
2846
3117
  * ...right sidebar (AI card, documents, etc.)...
2847
3118
  * </ng-container>
2848
3119
  *
2849
- * <ng-container ux-detail-footer>
2850
- * ...audit metadata row...
2851
- * </ng-container>
2852
3120
  * </ux-detail-layout>
2853
3121
  * ```
2854
3122
  */
@@ -2857,8 +3125,23 @@ class DetailLayoutComponent {
2857
3125
  tabs = input.required(...(ngDevMode ? [{ debugName: "tabs" }] : []));
2858
3126
  /** Currently active tab value (two-way bindable). */
2859
3127
  activeTab = model('', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
3128
+ /** Options for the mobile tab dropdown. */
3129
+ tabOptions = computed(() => this.tabs().map(t => ({ label: t.label, value: t.value })), ...(ngDevMode ? [{ debugName: "tabOptions" }] : []));
3130
+ /** True when there is only a single tab, making the tab bar redundant. */
3131
+ singleTab = computed(() => this.tabs().length <= 1, ...(ngDevMode ? [{ debugName: "singleTab" }] : []));
3132
+ /** True when viewport is below the lg breakpoint (1024px). */
3133
+ isMobile = signal(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
2860
3134
  /** True once the scrollable body has been scrolled past the threshold. */
2861
3135
  scrolled = signal(false, ...(ngDevMode ? [{ debugName: "scrolled" }] : []));
3136
+ constructor() {
3137
+ if (isPlatformBrowser(inject(PLATFORM_ID))) {
3138
+ const mql = window.matchMedia('(max-width: 1023px)');
3139
+ this.isMobile.set(mql.matches);
3140
+ const handler = (e) => this.isMobile.set(e.matches);
3141
+ mql.addEventListener('change', handler);
3142
+ inject(DestroyRef).onDestroy(() => mql.removeEventListener('change', handler));
3143
+ }
3144
+ }
2862
3145
  /** Tab content templates provided by the consumer. */
2863
3146
  tabTemplates;
2864
3147
  getTabTemplate(value) {
@@ -2870,8 +3153,8 @@ class DetailLayoutComponent {
2870
3153
  this.scrolled.set(el.scrollTop > 10);
2871
3154
  }
2872
3155
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DetailLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2873
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: DetailLayoutComponent, isStandalone: true, selector: "ux-detail-layout", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, activeTab: { classPropertyName: "activeTab", publicName: "activeTab", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTab: "activeTabChange" }, host: { classAttribute: "ux-detail-layout block" }, queries: [{ propertyName: "tabTemplates", predicate: DetailTabDirective }], ngImport: i0, template: `
2874
- <div class="flex flex-col overflow-hidden" [style.height]="'calc(100vh - 64px)'">
3156
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: DetailLayoutComponent, isStandalone: true, selector: "ux-detail-layout", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, activeTab: { classPropertyName: "activeTab", publicName: "activeTab", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTab: "activeTabChange" }, host: { classAttribute: "ux-detail-layout" }, queries: [{ propertyName: "tabTemplates", predicate: DetailTabDirective }], ngImport: i0, template: `
3157
+ <div class="flex flex-col flex-1 min-h-0">
2875
3158
 
2876
3159
  <!-- Sticky header (projected) -->
2877
3160
  <div class="ux-dl__header flex-shrink-0 z-10">
@@ -2883,13 +3166,30 @@ class DetailLayoutComponent {
2883
3166
  </div>
2884
3167
  </div>
2885
3168
 
2886
- <!-- Scrollable body -->
2887
- <div class="flex flex-col flex-1 min-h-0 overflow-y-auto overflow-x-hidden ux-dl__scroll"
2888
- (scroll)="onScroll($event)">
3169
+ <!-- p-tabs wraps tablist + scrollable panels for state binding -->
3170
+ <p-tabs class="flex flex-col flex-1 min-h-0" [value]="activeTab()" (valueChange)="activeTab.set($event + '')">
2889
3171
 
2890
- <!-- Full-width tab bar -->
2891
- <p-tabs [value]="activeTab()" (valueChange)="activeTab.set($event + '')">
2892
- <p-tablist>
3172
+ @if (!singleTab()) {
3173
+ <!-- Mobile: dropdown selector -->
3174
+ @if (isMobile()) {
3175
+ <div class="ux-dl__mobile-tabs">
3176
+ <p-select
3177
+ [options]="tabOptions()"
3178
+ [ngModel]="activeTab()"
3179
+ (ngModelChange)="activeTab.set($event)"
3180
+ optionLabel="label"
3181
+ optionValue="value"
3182
+ styleClass="w-full ux-dl__mobile-select"
3183
+ />
3184
+ </div>
3185
+ }
3186
+
3187
+ <!-- Desktop: horizontal tab bar (outside scroll → stays fixed below header) -->
3188
+ <p-tablist
3189
+ class="flex-shrink-0 ux-dl__tablist"
3190
+ [style.display]="isMobile() ? 'none' : null"
3191
+ [pt]="{ tabList: { class: 'p-0' } }"
3192
+ >
2893
3193
  @for (tab of tabs(); track tab.value) {
2894
3194
  <p-tab [value]="tab.value">
2895
3195
  @if (tab.icon) {
@@ -2899,12 +3199,17 @@ class DetailLayoutComponent {
2899
3199
  </p-tab>
2900
3200
  }
2901
3201
  </p-tablist>
3202
+ }
2902
3203
 
2903
- <!-- Content + Sidebar row below tab bar -->
2904
- <div class="flex flex-col lg:flex-row items-start gap-6 w-full py-4 sm:py-6">
3204
+ <!-- Scrollable body (only content scrolls, tabs stay above) -->
3205
+ <div class="flex flex-col flex-1 min-h-0 overflow-y-auto overflow-x-hidden ux-dl__scroll"
3206
+ (scroll)="onScroll($event)">
3207
+
3208
+ <!-- Content + Sidebar row -->
3209
+ <div class="flex flex-col lg:flex-row items-start gap-6 w-full py-4 lg:py-6">
2905
3210
 
2906
3211
  <!-- Main column: tab panels -->
2907
- <div class="flex-1 min-w-0 flex flex-col gap-6">
3212
+ <div class="w-full flex-1 min-w-0 flex flex-col gap-6">
2908
3213
  <p-tabpanels>
2909
3214
  @for (tab of tabs(); track tab.value) {
2910
3215
  <p-tabpanel [value]="tab.value">
@@ -2916,27 +3221,27 @@ class DetailLayoutComponent {
2916
3221
  </p-tabpanel>
2917
3222
  }
2918
3223
  </p-tabpanels>
2919
-
2920
- <!-- Footer below tab content -->
2921
- <ng-content select="[ux-detail-footer]" />
2922
3224
  </div>
2923
3225
 
2924
3226
  <!-- Sidebar -->
2925
- <aside class="w-full lg:w-[380px] shrink-0 flex flex-col lg:sticky lg:top-4 lg:self-start pb-8">
3227
+ <aside class="w-full lg:w-[380px] shrink-0 flex flex-col lg:sticky lg:top-4 lg:self-start lg:pb-8">
2926
3228
  <div class="ux-dl__sidebar-inner w-full">
2927
3229
  <ng-content select="[ux-detail-sidebar]" />
2928
3230
  </div>
2929
3231
  </aside>
2930
3232
  </div>
2931
- </p-tabs>
2932
- </div>
3233
+
3234
+ </div>
3235
+
3236
+ </p-tabs>
3237
+
2933
3238
  </div>
2934
- `, isInline: true, styles: [":host{display:block;min-height:calc(100vh - 4rem);font-family:var(--p-font-family, \"Noto Sans\", sans-serif);background:transparent;color:var(--p-text-color)}.ux-dl__header{background:transparent;border-bottom:1px solid var(--p-content-border-color)}.ux-dl__scroll{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-primary-color) 25%,transparent) color-mix(in srgb,var(--p-surface-500) 8%,transparent)}.ux-dl__scroll::-webkit-scrollbar{width:10px;height:10px}.ux-dl__scroll::-webkit-scrollbar-track{background:color-mix(in srgb,var(--p-surface-500) 8%,transparent);border-radius:var(--p-content-border-radius, .375rem)}.ux-dl__scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--p-primary-color) 25%,transparent);border-radius:var(--p-content-border-radius, .375rem)}.ux-dl__scroll::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--p-primary-color) 45%,transparent)}.ux-dl__sidebar-inner,.ux-dl__sidebar-inner>*{display:flex;flex-direction:column;gap:1.5rem}.ux-dl__header-meta{overflow:hidden;max-height:80px;opacity:1;transition:max-height .25s ease-out,opacity .2s ease-out}.ux-dl__header-meta--hidden{max-height:0;opacity:0}:host :deep p-tablist{position:sticky;top:0;z-index:5;background:var(--p-content-background, var(--p-primary-400))}:host :deep p-tablist .p-tablist-content{width:100%}:host :deep p-tablist .p-tablist-tab-list{width:100%}:host :deep p-tab{flex:1;justify-content:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2$2.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2$2.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2$2.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i2$2.TabList, selector: "p-tablist" }, { kind: "component", type: i2$2.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3239
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0;font-family:var(--p-font-family, \"Noto Sans\", sans-serif);background:transparent;color:var(--p-text-color)}.ux-dl__header{background:transparent}.ux-dl__scroll{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-primary-color) 25%,transparent) color-mix(in srgb,var(--p-surface-500) 8%,transparent)}.ux-dl__scroll::-webkit-scrollbar{width:10px;height:10px}.ux-dl__scroll::-webkit-scrollbar-track{background:color-mix(in srgb,var(--p-surface-500) 8%,transparent);border-radius:var(--p-content-border-radius, .375rem)}.ux-dl__scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--p-primary-color) 25%,transparent);border-radius:var(--p-content-border-radius, .375rem)}.ux-dl__scroll::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--p-primary-color) 45%,transparent)}.ux-dl__sidebar-inner{display:flex;flex-direction:column;gap:1.5rem}.ux-dl__header-meta{overflow:hidden;max-height:80px;opacity:1;transition:max-height .25s ease-out,opacity .2s ease-out}.ux-dl__header-meta--hidden{max-height:0;opacity:0}.ux-dl__tablist{display:flex;overflow:hidden}.ux-dl__mobile-tabs{position:sticky;top:0;z-index:5}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i4$4.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i4$4.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i4$4.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i4$4.TabList, selector: "p-tablist" }, { kind: "component", type: i4$4.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2935
3240
  }
2936
3241
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DetailLayoutComponent, decorators: [{
2937
3242
  type: Component,
2938
- args: [{ selector: 'ux-detail-layout', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, TabsModule], host: { class: 'ux-detail-layout block' }, template: `
2939
- <div class="flex flex-col overflow-hidden" [style.height]="'calc(100vh - 64px)'">
3243
+ args: [{ selector: 'ux-detail-layout', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, SelectModule, TabsModule], host: { class: 'ux-detail-layout' }, template: `
3244
+ <div class="flex flex-col flex-1 min-h-0">
2940
3245
 
2941
3246
  <!-- Sticky header (projected) -->
2942
3247
  <div class="ux-dl__header flex-shrink-0 z-10">
@@ -2948,13 +3253,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2948
3253
  </div>
2949
3254
  </div>
2950
3255
 
2951
- <!-- Scrollable body -->
2952
- <div class="flex flex-col flex-1 min-h-0 overflow-y-auto overflow-x-hidden ux-dl__scroll"
2953
- (scroll)="onScroll($event)">
3256
+ <!-- p-tabs wraps tablist + scrollable panels for state binding -->
3257
+ <p-tabs class="flex flex-col flex-1 min-h-0" [value]="activeTab()" (valueChange)="activeTab.set($event + '')">
2954
3258
 
2955
- <!-- Full-width tab bar -->
2956
- <p-tabs [value]="activeTab()" (valueChange)="activeTab.set($event + '')">
2957
- <p-tablist>
3259
+ @if (!singleTab()) {
3260
+ <!-- Mobile: dropdown selector -->
3261
+ @if (isMobile()) {
3262
+ <div class="ux-dl__mobile-tabs">
3263
+ <p-select
3264
+ [options]="tabOptions()"
3265
+ [ngModel]="activeTab()"
3266
+ (ngModelChange)="activeTab.set($event)"
3267
+ optionLabel="label"
3268
+ optionValue="value"
3269
+ styleClass="w-full ux-dl__mobile-select"
3270
+ />
3271
+ </div>
3272
+ }
3273
+
3274
+ <!-- Desktop: horizontal tab bar (outside scroll → stays fixed below header) -->
3275
+ <p-tablist
3276
+ class="flex-shrink-0 ux-dl__tablist"
3277
+ [style.display]="isMobile() ? 'none' : null"
3278
+ [pt]="{ tabList: { class: 'p-0' } }"
3279
+ >
2958
3280
  @for (tab of tabs(); track tab.value) {
2959
3281
  <p-tab [value]="tab.value">
2960
3282
  @if (tab.icon) {
@@ -2964,12 +3286,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2964
3286
  </p-tab>
2965
3287
  }
2966
3288
  </p-tablist>
3289
+ }
3290
+
3291
+ <!-- Scrollable body (only content scrolls, tabs stay above) -->
3292
+ <div class="flex flex-col flex-1 min-h-0 overflow-y-auto overflow-x-hidden ux-dl__scroll"
3293
+ (scroll)="onScroll($event)">
2967
3294
 
2968
- <!-- Content + Sidebar row below tab bar -->
2969
- <div class="flex flex-col lg:flex-row items-start gap-6 w-full py-4 sm:py-6">
3295
+ <!-- Content + Sidebar row -->
3296
+ <div class="flex flex-col lg:flex-row items-start gap-6 w-full py-4 lg:py-6">
2970
3297
 
2971
3298
  <!-- Main column: tab panels -->
2972
- <div class="flex-1 min-w-0 flex flex-col gap-6">
3299
+ <div class="w-full flex-1 min-w-0 flex flex-col gap-6">
2973
3300
  <p-tabpanels>
2974
3301
  @for (tab of tabs(); track tab.value) {
2975
3302
  <p-tabpanel [value]="tab.value">
@@ -2981,27 +3308,658 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2981
3308
  </p-tabpanel>
2982
3309
  }
2983
3310
  </p-tabpanels>
2984
-
2985
- <!-- Footer below tab content -->
2986
- <ng-content select="[ux-detail-footer]" />
2987
3311
  </div>
2988
3312
 
2989
3313
  <!-- Sidebar -->
2990
- <aside class="w-full lg:w-[380px] shrink-0 flex flex-col lg:sticky lg:top-4 lg:self-start pb-8">
3314
+ <aside class="w-full lg:w-[380px] shrink-0 flex flex-col lg:sticky lg:top-4 lg:self-start lg:pb-8">
2991
3315
  <div class="ux-dl__sidebar-inner w-full">
2992
3316
  <ng-content select="[ux-detail-sidebar]" />
2993
3317
  </div>
2994
3318
  </aside>
2995
3319
  </div>
2996
- </p-tabs>
2997
- </div>
3320
+
3321
+ </div>
3322
+
3323
+ </p-tabs>
3324
+
2998
3325
  </div>
2999
- `, styles: [":host{display:block;min-height:calc(100vh - 4rem);font-family:var(--p-font-family, \"Noto Sans\", sans-serif);background:transparent;color:var(--p-text-color)}.ux-dl__header{background:transparent;border-bottom:1px solid var(--p-content-border-color)}.ux-dl__scroll{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-primary-color) 25%,transparent) color-mix(in srgb,var(--p-surface-500) 8%,transparent)}.ux-dl__scroll::-webkit-scrollbar{width:10px;height:10px}.ux-dl__scroll::-webkit-scrollbar-track{background:color-mix(in srgb,var(--p-surface-500) 8%,transparent);border-radius:var(--p-content-border-radius, .375rem)}.ux-dl__scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--p-primary-color) 25%,transparent);border-radius:var(--p-content-border-radius, .375rem)}.ux-dl__scroll::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--p-primary-color) 45%,transparent)}.ux-dl__sidebar-inner,.ux-dl__sidebar-inner>*{display:flex;flex-direction:column;gap:1.5rem}.ux-dl__header-meta{overflow:hidden;max-height:80px;opacity:1;transition:max-height .25s ease-out,opacity .2s ease-out}.ux-dl__header-meta--hidden{max-height:0;opacity:0}:host :deep p-tablist{position:sticky;top:0;z-index:5;background:var(--p-content-background, var(--p-primary-400))}:host :deep p-tablist .p-tablist-content{width:100%}:host :deep p-tablist .p-tablist-tab-list{width:100%}:host :deep p-tab{flex:1;justify-content:center}\n"] }]
3000
- }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: false }] }, { type: i0.Output, args: ["activeTabChange"] }], tabTemplates: [{
3326
+ `, styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0;font-family:var(--p-font-family, \"Noto Sans\", sans-serif);background:transparent;color:var(--p-text-color)}.ux-dl__header{background:transparent}.ux-dl__scroll{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-primary-color) 25%,transparent) color-mix(in srgb,var(--p-surface-500) 8%,transparent)}.ux-dl__scroll::-webkit-scrollbar{width:10px;height:10px}.ux-dl__scroll::-webkit-scrollbar-track{background:color-mix(in srgb,var(--p-surface-500) 8%,transparent);border-radius:var(--p-content-border-radius, .375rem)}.ux-dl__scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--p-primary-color) 25%,transparent);border-radius:var(--p-content-border-radius, .375rem)}.ux-dl__scroll::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--p-primary-color) 45%,transparent)}.ux-dl__sidebar-inner{display:flex;flex-direction:column;gap:1.5rem}.ux-dl__header-meta{overflow:hidden;max-height:80px;opacity:1;transition:max-height .25s ease-out,opacity .2s ease-out}.ux-dl__header-meta--hidden{max-height:0;opacity:0}.ux-dl__tablist{display:flex;overflow:hidden}.ux-dl__mobile-tabs{position:sticky;top:0;z-index:5}\n"] }]
3327
+ }], ctorParameters: () => [], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: false }] }, { type: i0.Output, args: ["activeTabChange"] }], tabTemplates: [{
3001
3328
  type: ContentChildren,
3002
3329
  args: [DetailTabDirective]
3003
3330
  }] } });
3004
3331
 
3332
+ /**
3333
+ * Horizontal row of pill-shaped sub-tab buttons. Styling aligns with sidebar menu item
3334
+ * hover/active tokens (`--d-menuitem-*`).
3335
+ */
3336
+ class PillTabsComponent {
3337
+ items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
3338
+ activeValue = model('', ...(ngDevMode ? [{ debugName: "activeValue" }] : []));
3339
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: PillTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3340
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: PillTabsComponent, isStandalone: true, selector: "ux-pill-tabs", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, activeValue: { classPropertyName: "activeValue", publicName: "activeValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeValue: "activeValueChange" }, host: { classAttribute: "ux-pill-tabs block" }, ngImport: i0, template: `
3341
+ <div class="ux-pill-tabs__row" role="tablist">
3342
+ @for (item of items(); track item.value) {
3343
+ <button
3344
+ type="button"
3345
+ class="ux-pill-tabs__btn"
3346
+ role="tab"
3347
+ [attr.aria-selected]="activeValue() === item.value"
3348
+ [class.ux-pill-tabs__btn--active]="activeValue() === item.value"
3349
+ (click)="activeValue.set(item.value)"
3350
+ >
3351
+ {{ item.label }}
3352
+ </button>
3353
+ }
3354
+ </div>
3355
+ `, isInline: true, styles: [".ux-pill-tabs__row{display:flex;flex-wrap:wrap;gap:.5rem}.ux-pill-tabs__btn{border-radius:9999px;padding:.375rem .875rem;font-size:var(--p-font-size, .875rem);font-weight:600;font-family:inherit;cursor:pointer;transition:all .15s ease;border:1px solid var(--p-primary-800);background:transparent;color:var(--p-text-muted-color)}:host-context(.app-dark) .ux-pill-tabs__btn:not(.ux-pill-tabs__btn--active){border-color:var(--p-surface-700)}.ux-pill-tabs__btn:hover:not(.ux-pill-tabs__btn--active){background:var(--d-menuitem-hover-bg);color:var(--p-primary-950)}:host-context(.app-dark) .ux-pill-tabs__btn:hover:not(.ux-pill-tabs__btn--active){color:var(--p-primary-200)}.ux-pill-tabs__btn--active{background:var(--p-primary-200);border-color:transparent;color:var(--p-primary-950)}:host-context(.app-dark) .ux-pill-tabs__btn--active{color:var(--p-primary-950)}.ux-pill-tabs__btn:focus{outline:none;box-shadow:none}.ux-pill-tabs__btn:focus-visible{outline:none;box-shadow:var(--d-menuitem-focus-shadow)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3356
+ }
3357
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: PillTabsComponent, decorators: [{
3358
+ type: Component,
3359
+ args: [{ selector: 'ux-pill-tabs', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'ux-pill-tabs block' }, template: `
3360
+ <div class="ux-pill-tabs__row" role="tablist">
3361
+ @for (item of items(); track item.value) {
3362
+ <button
3363
+ type="button"
3364
+ class="ux-pill-tabs__btn"
3365
+ role="tab"
3366
+ [attr.aria-selected]="activeValue() === item.value"
3367
+ [class.ux-pill-tabs__btn--active]="activeValue() === item.value"
3368
+ (click)="activeValue.set(item.value)"
3369
+ >
3370
+ {{ item.label }}
3371
+ </button>
3372
+ }
3373
+ </div>
3374
+ `, styles: [".ux-pill-tabs__row{display:flex;flex-wrap:wrap;gap:.5rem}.ux-pill-tabs__btn{border-radius:9999px;padding:.375rem .875rem;font-size:var(--p-font-size, .875rem);font-weight:600;font-family:inherit;cursor:pointer;transition:all .15s ease;border:1px solid var(--p-primary-800);background:transparent;color:var(--p-text-muted-color)}:host-context(.app-dark) .ux-pill-tabs__btn:not(.ux-pill-tabs__btn--active){border-color:var(--p-surface-700)}.ux-pill-tabs__btn:hover:not(.ux-pill-tabs__btn--active){background:var(--d-menuitem-hover-bg);color:var(--p-primary-950)}:host-context(.app-dark) .ux-pill-tabs__btn:hover:not(.ux-pill-tabs__btn--active){color:var(--p-primary-200)}.ux-pill-tabs__btn--active{background:var(--p-primary-200);border-color:transparent;color:var(--p-primary-950)}:host-context(.app-dark) .ux-pill-tabs__btn--active{color:var(--p-primary-950)}.ux-pill-tabs__btn:focus{outline:none;box-shadow:none}.ux-pill-tabs__btn:focus-visible{outline:none;box-shadow:var(--d-menuitem-focus-shadow)}\n"] }]
3375
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], activeValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeValue", required: false }] }, { type: i0.Output, args: ["activeValueChange"] }] } });
3376
+
3377
+ class DocumentsCardComponent {
3378
+ documents = input([], ...(ngDevMode ? [{ debugName: "documents" }] : []));
3379
+ rows = input(5, ...(ngDevMode ? [{ debugName: "rows" }] : []));
3380
+ expanded = signal(false, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
3381
+ activeFilter = signal('All Files', ...(ngDevMode ? [{ debugName: "activeFilter" }] : []));
3382
+ searchQuery = model('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
3383
+ fileTypes = computed(() => {
3384
+ const types = [...new Set(this.documents().map(d => d.type))];
3385
+ types.sort();
3386
+ return types;
3387
+ }, ...(ngDevMode ? [{ debugName: "fileTypes" }] : []));
3388
+ filterOptions = computed(() => ['All Files', ...this.fileTypes(), 'Other'], ...(ngDevMode ? [{ debugName: "filterOptions" }] : []));
3389
+ pillTabItems = computed(() => this.filterOptions().map(f => ({ value: f, label: f })), ...(ngDevMode ? [{ debugName: "pillTabItems" }] : []));
3390
+ summary = computed(() => {
3391
+ const docs = this.documents();
3392
+ const count = docs.length;
3393
+ if (count === 0)
3394
+ return 'No files attached';
3395
+ const types = this.fileTypes();
3396
+ const fileWord = count === 1 ? 'file' : 'files';
3397
+ if (types.length === 0)
3398
+ return `${count} ${fileWord} attached`;
3399
+ return `${count} ${fileWord} · ${types.join(', ')}`;
3400
+ }, ...(ngDevMode ? [{ debugName: "summary" }] : []));
3401
+ filtered = computed(() => {
3402
+ let docs = this.documents();
3403
+ const query = this.searchQuery().trim().toLowerCase();
3404
+ if (query) {
3405
+ docs = docs.filter(d => d.fileName.toLowerCase().includes(query) || d.owner.toLowerCase().includes(query));
3406
+ }
3407
+ const filter = this.activeFilter();
3408
+ if (filter === 'All Files')
3409
+ return docs;
3410
+ if (filter === 'Other') {
3411
+ const knownTypes = this.fileTypes();
3412
+ return docs.filter(d => !knownTypes.includes(d.type));
3413
+ }
3414
+ return docs.filter(d => d.type === filter);
3415
+ }, ...(ngDevMode ? [{ debugName: "filtered" }] : []));
3416
+ menuItems = [];
3417
+ onMenuToggle(event, _doc, menu) {
3418
+ this.menuItems = [
3419
+ { label: 'Preview', icon: 'pi pi-eye' },
3420
+ { label: 'Share', icon: 'pi pi-share-alt' },
3421
+ { separator: true },
3422
+ { label: 'Delete', icon: 'pi pi-trash', styleClass: 'text-red-500' }
3423
+ ];
3424
+ menu.toggle(event);
3425
+ }
3426
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DocumentsCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3427
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: DocumentsCardComponent, isStandalone: true, selector: "ux-documents-card", inputs: { documents: { classPropertyName: "documents", publicName: "documents", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchQuery: "searchQueryChange" }, host: { classAttribute: "ux-documents-card block" }, ngImport: i0, template: `
3428
+ <div class="card flex flex-col overflow-hidden">
3429
+ <p-panel [toggleable]="true" [collapsed]="!expanded()" (collapsedChange)="expanded.set(!$event)" toggler="header">
3430
+ <ng-template #headericons let-collapsed>
3431
+ <i class="pi text-sm text-darkblue-500 dark:text-surface-0" [ngClass]="collapsed ? 'pi-chevron-down' : 'pi-chevron-up'"></i>
3432
+ </ng-template>
3433
+ <ng-template #header>
3434
+ <div class="flex items-center gap-3 flex-1">
3435
+ <div class="w-[34px] h-[34px] rounded-[10px] flex items-center justify-center shrink-0">
3436
+ <i class="pi pi-folder text-deepsea-500 dark:text-surface-0"></i>
3437
+ </div>
3438
+ <div class="flex flex-col">
3439
+ <h4 class="title-h4 text-left text-deepsea-500 dark:text-surface-0">Documents</h4>
3440
+ <span class="text-surface-500 dark:text-surface-300 text-sm font-medium leading-tight">{{ summary() }}</span>
3441
+ </div>
3442
+ </div>
3443
+ </ng-template>
3444
+ <div class="flex flex-col gap-4 pt-4">
3445
+ <ux-pill-tabs [items]="pillTabItems()" [(activeValue)]="activeFilter" />
3446
+
3447
+ <p-iconfield>
3448
+ <p-inputicon class="pi pi-search" />
3449
+ <input pInputText [(ngModel)]="searchQuery" placeholder="Search documents" class="w-full" />
3450
+ </p-iconfield>
3451
+
3452
+ @if (filtered().length > 0) {
3453
+ <p-table
3454
+ [value]="filtered()"
3455
+ [paginator]="true"
3456
+ [rows]="rows()"
3457
+ sortMode="multiple"
3458
+ styleClass="flex flex-col rounded-2xl overflow-hidden [&>[data-pc-section=paginatorcontainer]]:border-0! [&>[data-pc-section=paginatorcontainer]]:mt-auto [&_[data-pc-name=pcpaginator]]:rounded-none!"
3459
+ tableStyleClass="w-full table-fixed"
3460
+ paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink"
3461
+ >
3462
+ <ng-template #header>
3463
+ <tr>
3464
+ <th pSortableColumn="fileName" class="w-1/2">File Name <p-sortIcon field="fileName" /></th>
3465
+ <th pSortableColumn="type" class="w-1/4">Type <p-sortIcon field="type" /></th>
3466
+ <th class="w-1/4">Actions</th>
3467
+ </tr>
3468
+ </ng-template>
3469
+ <ng-template #body let-doc>
3470
+ <tr>
3471
+ <td>
3472
+ <div class="flex items-center gap-3 py-1 min-w-0">
3473
+ <i class="pi text-xl text-surface-600 dark:text-surface-300 shrink-0" [ngClass]="doc.icon"></i>
3474
+ <span class="text-surface-700 dark:text-surface-100 text-sm truncate">{{ doc.fileName }}</span>
3475
+ </div>
3476
+ </td>
3477
+ <td>
3478
+ <p-tag [value]="doc.type" styleClass="px-2 py-1" />
3479
+ </td>
3480
+ <td>
3481
+ <div class="flex items-center gap-1">
3482
+ <p-button icon="pi pi-download" [rounded]="true" [text]="true" size="small" severity="secondary" styleClass="cursor-pointer" ariaLabel="Download" />
3483
+ <p-button icon="pi pi-ellipsis-h" [rounded]="true" [text]="true" size="small" severity="secondary" styleClass="cursor-pointer" ariaLabel="More options" (onClick)="onMenuToggle($event, doc, docMenu)" />
3484
+ <p-menu #docMenu [model]="menuItems" [popup]="true" styleClass="w-48" appendTo="body" />
3485
+ </div>
3486
+ </td>
3487
+ </tr>
3488
+ </ng-template>
3489
+ </p-table>
3490
+ } @else {
3491
+ <div class="flex flex-col items-center gap-3 py-8 text-center">
3492
+ <i class="pi pi-folder-open text-3xl text-surface-300 dark:text-surface-500"></i>
3493
+ <span class="text-surface-600 dark:text-surface-300 text-sm">No documents to show</span>
3494
+ </div>
3495
+ }
3496
+
3497
+ <p-fileupload
3498
+ name="documents[]"
3499
+ [multiple]="true"
3500
+ maxFileSize="10000000"
3501
+ mode="advanced"
3502
+ [auto]="false"
3503
+ chooseLabel="Upload File"
3504
+ chooseIcon="pi pi-upload"
3505
+ [showUploadButton]="false"
3506
+ [showCancelButton]="false"
3507
+ [pt]="{ root: { class: 'bg-transparent' }, header: { class: 'bg-transparent' }, content: { class: 'bg-transparent' } }"
3508
+ >
3509
+ <ng-template #header let-chooseCallback="chooseCallback">
3510
+ <div class="flex items-center gap-2 w-full">
3511
+ <p-button icon="pi pi-upload" label="Upload File" (onClick)="chooseCallback()" />
3512
+ <p-button icon="pi pi-link" label="Share Link" [outlined]="true" styleClass="!text-primary-600 !border-primary-600" />
3513
+ </div>
3514
+ </ng-template>
3515
+ <ng-template #empty>
3516
+ <div class="flex flex-col items-center gap-2 py-4">
3517
+ <i class="pi pi-cloud-upload text-2xl text-surface-400 dark:text-surface-300"></i>
3518
+ <span class="text-surface-500 dark:text-surface-100 text-sm">Drag and drop files here</span>
3519
+ </div>
3520
+ </ng-template>
3521
+ </p-fileupload>
3522
+ </div>
3523
+ </p-panel>
3524
+ </div>
3525
+ `, isInline: true, styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i3$4.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "ngmodule", type: IconFieldModule }, { kind: "component", type: i4$3.IconField, selector: "p-iconfield, p-iconField, p-icon-field", inputs: ["hostName", "iconPosition", "styleClass"] }, { kind: "ngmodule", type: InputIconModule }, { kind: "component", type: i5$1.InputIcon, selector: "p-inputicon, p-inputIcon", inputs: ["hostName", "styleClass"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: MenuModule }, { kind: "component", type: i7$1.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i8$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i8$1.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "component", type: i8$1.SortIcon, selector: "p-sortIcon", inputs: ["field"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i9$1.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "component", type: PillTabsComponent, selector: "ux-pill-tabs", inputs: ["items", "activeValue"], outputs: ["activeValueChange"] }, { kind: "ngmodule", type: PanelModule }, { kind: "component", type: i10.Panel, selector: "p-panel", inputs: ["id", "toggleable", "header", "collapsed", "styleClass", "iconPos", "showHeader", "toggler", "transitionOptions", "toggleButtonProps", "motionOptions"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3526
+ }
3527
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DocumentsCardComponent, decorators: [{
3528
+ type: Component,
3529
+ args: [{ selector: 'ux-documents-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass, FormsModule, ButtonModule, FileUploadModule, IconFieldModule, InputIconModule, InputTextModule, MenuModule, TableModule, TagModule, PillTabsComponent, PanelModule], host: { class: 'ux-documents-card block' }, template: `
3530
+ <div class="card flex flex-col overflow-hidden">
3531
+ <p-panel [toggleable]="true" [collapsed]="!expanded()" (collapsedChange)="expanded.set(!$event)" toggler="header">
3532
+ <ng-template #headericons let-collapsed>
3533
+ <i class="pi text-sm text-darkblue-500 dark:text-surface-0" [ngClass]="collapsed ? 'pi-chevron-down' : 'pi-chevron-up'"></i>
3534
+ </ng-template>
3535
+ <ng-template #header>
3536
+ <div class="flex items-center gap-3 flex-1">
3537
+ <div class="w-[34px] h-[34px] rounded-[10px] flex items-center justify-center shrink-0">
3538
+ <i class="pi pi-folder text-deepsea-500 dark:text-surface-0"></i>
3539
+ </div>
3540
+ <div class="flex flex-col">
3541
+ <h4 class="title-h4 text-left text-deepsea-500 dark:text-surface-0">Documents</h4>
3542
+ <span class="text-surface-500 dark:text-surface-300 text-sm font-medium leading-tight">{{ summary() }}</span>
3543
+ </div>
3544
+ </div>
3545
+ </ng-template>
3546
+ <div class="flex flex-col gap-4 pt-4">
3547
+ <ux-pill-tabs [items]="pillTabItems()" [(activeValue)]="activeFilter" />
3548
+
3549
+ <p-iconfield>
3550
+ <p-inputicon class="pi pi-search" />
3551
+ <input pInputText [(ngModel)]="searchQuery" placeholder="Search documents" class="w-full" />
3552
+ </p-iconfield>
3553
+
3554
+ @if (filtered().length > 0) {
3555
+ <p-table
3556
+ [value]="filtered()"
3557
+ [paginator]="true"
3558
+ [rows]="rows()"
3559
+ sortMode="multiple"
3560
+ styleClass="flex flex-col rounded-2xl overflow-hidden [&>[data-pc-section=paginatorcontainer]]:border-0! [&>[data-pc-section=paginatorcontainer]]:mt-auto [&_[data-pc-name=pcpaginator]]:rounded-none!"
3561
+ tableStyleClass="w-full table-fixed"
3562
+ paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink"
3563
+ >
3564
+ <ng-template #header>
3565
+ <tr>
3566
+ <th pSortableColumn="fileName" class="w-1/2">File Name <p-sortIcon field="fileName" /></th>
3567
+ <th pSortableColumn="type" class="w-1/4">Type <p-sortIcon field="type" /></th>
3568
+ <th class="w-1/4">Actions</th>
3569
+ </tr>
3570
+ </ng-template>
3571
+ <ng-template #body let-doc>
3572
+ <tr>
3573
+ <td>
3574
+ <div class="flex items-center gap-3 py-1 min-w-0">
3575
+ <i class="pi text-xl text-surface-600 dark:text-surface-300 shrink-0" [ngClass]="doc.icon"></i>
3576
+ <span class="text-surface-700 dark:text-surface-100 text-sm truncate">{{ doc.fileName }}</span>
3577
+ </div>
3578
+ </td>
3579
+ <td>
3580
+ <p-tag [value]="doc.type" styleClass="px-2 py-1" />
3581
+ </td>
3582
+ <td>
3583
+ <div class="flex items-center gap-1">
3584
+ <p-button icon="pi pi-download" [rounded]="true" [text]="true" size="small" severity="secondary" styleClass="cursor-pointer" ariaLabel="Download" />
3585
+ <p-button icon="pi pi-ellipsis-h" [rounded]="true" [text]="true" size="small" severity="secondary" styleClass="cursor-pointer" ariaLabel="More options" (onClick)="onMenuToggle($event, doc, docMenu)" />
3586
+ <p-menu #docMenu [model]="menuItems" [popup]="true" styleClass="w-48" appendTo="body" />
3587
+ </div>
3588
+ </td>
3589
+ </tr>
3590
+ </ng-template>
3591
+ </p-table>
3592
+ } @else {
3593
+ <div class="flex flex-col items-center gap-3 py-8 text-center">
3594
+ <i class="pi pi-folder-open text-3xl text-surface-300 dark:text-surface-500"></i>
3595
+ <span class="text-surface-600 dark:text-surface-300 text-sm">No documents to show</span>
3596
+ </div>
3597
+ }
3598
+
3599
+ <p-fileupload
3600
+ name="documents[]"
3601
+ [multiple]="true"
3602
+ maxFileSize="10000000"
3603
+ mode="advanced"
3604
+ [auto]="false"
3605
+ chooseLabel="Upload File"
3606
+ chooseIcon="pi pi-upload"
3607
+ [showUploadButton]="false"
3608
+ [showCancelButton]="false"
3609
+ [pt]="{ root: { class: 'bg-transparent' }, header: { class: 'bg-transparent' }, content: { class: 'bg-transparent' } }"
3610
+ >
3611
+ <ng-template #header let-chooseCallback="chooseCallback">
3612
+ <div class="flex items-center gap-2 w-full">
3613
+ <p-button icon="pi pi-upload" label="Upload File" (onClick)="chooseCallback()" />
3614
+ <p-button icon="pi pi-link" label="Share Link" [outlined]="true" styleClass="!text-primary-600 !border-primary-600" />
3615
+ </div>
3616
+ </ng-template>
3617
+ <ng-template #empty>
3618
+ <div class="flex flex-col items-center gap-2 py-4">
3619
+ <i class="pi pi-cloud-upload text-2xl text-surface-400 dark:text-surface-300"></i>
3620
+ <span class="text-surface-500 dark:text-surface-100 text-sm">Drag and drop files here</span>
3621
+ </div>
3622
+ </ng-template>
3623
+ </p-fileupload>
3624
+ </div>
3625
+ </p-panel>
3626
+ </div>
3627
+ ` }]
3628
+ }], propDecorators: { documents: [{ type: i0.Input, args: [{ isSignal: true, alias: "documents", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }, { type: i0.Output, args: ["searchQueryChange"] }] } });
3629
+
3630
+ class UxSelectComponent {
3631
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
3632
+ optionLabel = input('label', ...(ngDevMode ? [{ debugName: "optionLabel" }] : []));
3633
+ optionValue = input('value', ...(ngDevMode ? [{ debugName: "optionValue" }] : []));
3634
+ optionGroupLabel = input('label', ...(ngDevMode ? [{ debugName: "optionGroupLabel" }] : []));
3635
+ optionGroupChildren = input('items', ...(ngDevMode ? [{ debugName: "optionGroupChildren" }] : []));
3636
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
3637
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
3638
+ filter = input(false, ...(ngDevMode ? [{ debugName: "filter" }] : []));
3639
+ showClear = input(false, ...(ngDevMode ? [{ debugName: "showClear" }] : []));
3640
+ emptyMessage = input('No results found', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
3641
+ group = input(false, ...(ngDevMode ? [{ debugName: "group" }] : []));
3642
+ styleClass = input('', ...(ngDevMode ? [{ debugName: "styleClass" }] : []));
3643
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
3644
+ onChange = output();
3645
+ onFilter = output();
3646
+ resolvedStyleClass = computed(() => {
3647
+ const base = 'ux-select__inner';
3648
+ const extra = this.styleClass();
3649
+ return extra ? `${base} ${extra}` : base;
3650
+ }, ...(ngDevMode ? [{ debugName: "resolvedStyleClass" }] : []));
3651
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: UxSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3652
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.8", type: UxSelectComponent, isStandalone: true, selector: "ux-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: false, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: false, transformFunction: null }, optionGroupLabel: { classPropertyName: "optionGroupLabel", publicName: "optionGroupLabel", isSignal: true, isRequired: false, transformFunction: null }, optionGroupChildren: { classPropertyName: "optionGroupChildren", publicName: "optionGroupChildren", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onChange: "onChange", onFilter: "onFilter" }, host: { classAttribute: "ux-select" }, ngImport: i0, template: `
3653
+ <p-select
3654
+ [options]="options()"
3655
+ [optionLabel]="optionLabel()"
3656
+ [optionValue]="optionValue()"
3657
+ [optionGroupLabel]="optionGroupLabel()"
3658
+ [optionGroupChildren]="optionGroupChildren()"
3659
+ [placeholder]="placeholder()"
3660
+ [disabled]="disabled()"
3661
+ [filter]="filter()"
3662
+ [showClear]="showClear()"
3663
+ [emptyMessage]="emptyMessage()"
3664
+ [group]="group()"
3665
+ [ngModel]="value()"
3666
+ (ngModelChange)="value.set($event)"
3667
+ (onChange)="onChange.emit($event)"
3668
+ (onFilter)="onFilter.emit($event)"
3669
+ [styleClass]="resolvedStyleClass()"
3670
+ />
3671
+ `, isInline: true, styles: [":host :deep .p-select{border-radius:var(--p-content-border-radius, .375rem);font-family:var(--p-font-family, \"Noto Sans\", sans-serif);font-size:var(--font-size-sm, .875rem);transition:border-color .15s ease,box-shadow .15s ease;padding:.5rem 1.5rem}:host :deep .p-select:not(.p-disabled):hover{border-color:var(--p-primary-400)}:host :deep .p-select:not(.p-disabled).p-focus{border-color:var(--p-primary-500);box-shadow:0 0 0 2px color-mix(in srgb,var(--p-primary-500) 20%,transparent)}:host :deep .p-select-label{font-size:var(--font-size-sm, .875rem);color:var(--p-text-color)}:host :deep .p-select-label.p-placeholder{color:var(--p-text-muted-color)}:host :deep .p-select-dropdown{color:var(--p-text-muted-color)}:host-context(:root[class*=\"app-dark\"]) :deep .p-select:not(.p-disabled):hover{border-color:var(--p-primary-300)}:host-context(:root[class*=\"app-dark\"]) :deep .p-select:not(.p-disabled).p-focus{border-color:var(--p-primary-400);box-shadow:0 0 0 2px color-mix(in srgb,var(--p-primary-400) 25%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3672
+ }
3673
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: UxSelectComponent, decorators: [{
3674
+ type: Component,
3675
+ args: [{ selector: 'ux-select', changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormsModule, SelectModule], host: { class: 'ux-select' }, template: `
3676
+ <p-select
3677
+ [options]="options()"
3678
+ [optionLabel]="optionLabel()"
3679
+ [optionValue]="optionValue()"
3680
+ [optionGroupLabel]="optionGroupLabel()"
3681
+ [optionGroupChildren]="optionGroupChildren()"
3682
+ [placeholder]="placeholder()"
3683
+ [disabled]="disabled()"
3684
+ [filter]="filter()"
3685
+ [showClear]="showClear()"
3686
+ [emptyMessage]="emptyMessage()"
3687
+ [group]="group()"
3688
+ [ngModel]="value()"
3689
+ (ngModelChange)="value.set($event)"
3690
+ (onChange)="onChange.emit($event)"
3691
+ (onFilter)="onFilter.emit($event)"
3692
+ [styleClass]="resolvedStyleClass()"
3693
+ />
3694
+ `, styles: [":host :deep .p-select{border-radius:var(--p-content-border-radius, .375rem);font-family:var(--p-font-family, \"Noto Sans\", sans-serif);font-size:var(--font-size-sm, .875rem);transition:border-color .15s ease,box-shadow .15s ease;padding:.5rem 1.5rem}:host :deep .p-select:not(.p-disabled):hover{border-color:var(--p-primary-400)}:host :deep .p-select:not(.p-disabled).p-focus{border-color:var(--p-primary-500);box-shadow:0 0 0 2px color-mix(in srgb,var(--p-primary-500) 20%,transparent)}:host :deep .p-select-label{font-size:var(--font-size-sm, .875rem);color:var(--p-text-color)}:host :deep .p-select-label.p-placeholder{color:var(--p-text-muted-color)}:host :deep .p-select-dropdown{color:var(--p-text-muted-color)}:host-context(:root[class*=\"app-dark\"]) :deep .p-select:not(.p-disabled):hover{border-color:var(--p-primary-300)}:host-context(:root[class*=\"app-dark\"]) :deep .p-select:not(.p-disabled).p-focus{border-color:var(--p-primary-400);box-shadow:0 0 0 2px color-mix(in srgb,var(--p-primary-400) 25%,transparent)}\n"] }]
3695
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: false }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], optionGroupLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroupLabel", required: false }] }], optionGroupChildren: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroupChildren", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], styleClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "styleClass", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onChange: [{ type: i0.Output, args: ["onChange"] }], onFilter: [{ type: i0.Output, args: ["onFilter"] }] } });
3696
+
3697
+ class TaskDrawerComponent {
3698
+ visible = model(false, ...(ngDevMode ? [{ debugName: "visible" }] : []));
3699
+ task = input(null, ...(ngDevMode ? [{ debugName: "task" }] : []));
3700
+ mode = input('create', ...(ngDevMode ? [{ debugName: "mode" }] : []));
3701
+ availableMembers = input([
3702
+ { name: 'Amy Elsner', image: 'amyelsner.png' },
3703
+ { name: 'Anna Fali', image: 'annafali.png' },
3704
+ { name: 'Asiya Javayant', image: 'asiyajavayant.png' },
3705
+ { name: 'Bernardo Dominic', image: 'bernardodominic.png' }
3706
+ ], ...(ngDevMode ? [{ debugName: "availableMembers" }] : []));
3707
+ save = output();
3708
+ cancel = output();
3709
+ drawerTitle = computed(() => this.mode() === 'create' ? 'Create New Task' : 'Edit Task', ...(ngDevMode ? [{ debugName: "drawerTitle" }] : []));
3710
+ formData = {
3711
+ id: null,
3712
+ title: '',
3713
+ description: '',
3714
+ status: 'pending',
3715
+ completed: false,
3716
+ startDate: null,
3717
+ endDate: null,
3718
+ members: []
3719
+ };
3720
+ statusOptions = [
3721
+ { label: 'Pending', value: 'pending' },
3722
+ { label: 'In Progress', value: 'in-progress' },
3723
+ { label: 'Completed', value: 'completed' }
3724
+ ];
3725
+ filteredMembers = [];
3726
+ ngOnChanges(changes) {
3727
+ if (changes['task']) {
3728
+ const newTask = changes['task'].currentValue;
3729
+ if (newTask) {
3730
+ this.formData = {
3731
+ id: newTask.id,
3732
+ title: newTask.title || '',
3733
+ description: newTask.description || '',
3734
+ status: newTask.status || 'pending',
3735
+ completed: newTask.completed || false,
3736
+ startDate: newTask.startDate ? this.parseDate(newTask.startDate) : null,
3737
+ endDate: newTask.endDate ? this.parseDate(newTask.endDate) : null,
3738
+ members: newTask.members || []
3739
+ };
3740
+ }
3741
+ else {
3742
+ this.resetForm();
3743
+ }
3744
+ }
3745
+ }
3746
+ parseDate(dateStr) {
3747
+ if (!dateStr)
3748
+ return null;
3749
+ const parts = dateStr.split('.');
3750
+ if (parts.length === 3) {
3751
+ return new Date(parseInt(parts[2]), parseInt(parts[1]) - 1, parseInt(parts[0]));
3752
+ }
3753
+ return null;
3754
+ }
3755
+ resetForm() {
3756
+ this.formData = {
3757
+ id: null,
3758
+ title: '',
3759
+ description: '',
3760
+ status: 'pending',
3761
+ completed: false,
3762
+ startDate: null,
3763
+ endDate: null,
3764
+ members: []
3765
+ };
3766
+ }
3767
+ filterMembers(event) {
3768
+ const members = this.availableMembers();
3769
+ if (!event.query) {
3770
+ this.filteredMembers = members;
3771
+ return;
3772
+ }
3773
+ this.filteredMembers = members.filter(member => member.name?.toLowerCase().includes(event.query.toLowerCase()));
3774
+ }
3775
+ formatDateForSave(date) {
3776
+ if (!date)
3777
+ return null;
3778
+ const d = new Date(date);
3779
+ return `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${d.getFullYear()}`;
3780
+ }
3781
+ handleSave() {
3782
+ const taskData = {
3783
+ id: this.formData.id,
3784
+ title: this.formData.title,
3785
+ description: this.formData.description || null,
3786
+ status: this.formData.status,
3787
+ completed: this.formData.status === 'completed',
3788
+ startDate: this.formatDateForSave(this.formData.startDate),
3789
+ endDate: this.formatDateForSave(this.formData.endDate),
3790
+ members: this.formData.members
3791
+ };
3792
+ this.save.emit(taskData);
3793
+ this.handleCancel();
3794
+ }
3795
+ handleCancel() {
3796
+ this.resetForm();
3797
+ this.cancel.emit();
3798
+ this.visible.set(false);
3799
+ }
3800
+ onHide() {
3801
+ this.handleCancel();
3802
+ }
3803
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TaskDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3804
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.8", type: TaskDrawerComponent, isStandalone: true, selector: "ux-task-drawer", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, task: { classPropertyName: "task", publicName: "task", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, availableMembers: { classPropertyName: "availableMembers", publicName: "availableMembers", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", save: "save", cancel: "cancel" }, host: { classAttribute: "ux-task-drawer" }, usesOnChanges: true, ngImport: i0, template: `
3805
+ <p-drawer [visible]="visible()" position="right" styleClass="w-full! md:w-[420px]!" (onHide)="onHide()" (visibleChange)="visible.set($event)" appendTo="body">
3806
+ <ng-template #header>
3807
+ <div class="flex items-center gap-3">
3808
+ <i class="pi pi-list-check text-xl text-primary-500"></i>
3809
+ <span class="text-surface-900 dark:text-surface-0 font-semibold text-lg">{{ drawerTitle() }}</span>
3810
+ </div>
3811
+ </ng-template>
3812
+
3813
+ <div class="flex flex-col gap-6 p-1">
3814
+ <div class="flex flex-col gap-2">
3815
+ <label for="task-title" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Task Title</label>
3816
+ <input pInputText id="task-title" [(ngModel)]="formData.title" placeholder="Enter task title..." class="w-full" />
3817
+ </div>
3818
+
3819
+ <div class="flex flex-col gap-2">
3820
+ <label for="task-description" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Description</label>
3821
+ <textarea pTextarea id="task-description" [(ngModel)]="formData.description" placeholder="Enter task description..." [rows]="4" class="w-full"></textarea>
3822
+ </div>
3823
+
3824
+ <div class="flex flex-col gap-2">
3825
+ <label for="task-status" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Status</label>
3826
+ <p-select id="task-status" [(ngModel)]="formData.status" [options]="statusOptions" optionLabel="label" optionValue="value" placeholder="Select status" styleClass="w-full" />
3827
+ </div>
3828
+
3829
+ <p-divider styleClass="my-2" />
3830
+
3831
+ <div class="flex flex-col gap-2">
3832
+ <label for="start-date" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Start Date</label>
3833
+ <p-datepicker id="start-date" [(ngModel)]="formData.startDate" dateFormat="dd.mm.yy" placeholder="Select start date" inputStyleClass="w-full" />
3834
+ </div>
3835
+
3836
+ <div class="flex flex-col gap-2">
3837
+ <label for="end-date" class="text-surface-900 dark:text-surface-0 font-medium text-sm">End Date</label>
3838
+ <p-datepicker id="end-date" [(ngModel)]="formData.endDate" dateFormat="dd.mm.yy" placeholder="Select end date" inputStyleClass="w-full" />
3839
+ </div>
3840
+
3841
+ <p-divider styleClass="my-2" />
3842
+
3843
+ <div class="flex flex-col gap-2">
3844
+ <label for="team-members" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Team Members</label>
3845
+ <p-autocomplete
3846
+ id="team-members"
3847
+ [(ngModel)]="formData.members"
3848
+ [suggestions]="filteredMembers"
3849
+ optionLabel="name"
3850
+ [multiple]="true"
3851
+ placeholder="Search team members..."
3852
+ (completeMethod)="filterMembers($event)"
3853
+ styleClass="w-full"
3854
+ >
3855
+ <ng-template #selecteditem let-value>
3856
+ <div class="flex items-center gap-2 bg-surface-50 dark:bg-surface-900 px-2 py-1 rounded">
3857
+ <p-avatar [image]="'demo/images/avatar/' + value.image" shape="circle" styleClass="w-5 h-5 border border-surface-200 dark:border-surface-700" />
3858
+ </div>
3859
+ </ng-template>
3860
+ <ng-template #item let-option>
3861
+ <div class="flex items-center gap-3">
3862
+ <p-avatar [image]="'demo/images/avatar/' + option.image" shape="circle" styleClass="w-8 h-8 border border-surface-200 dark:border-surface-700" />
3863
+ <span class="text-surface-900 dark:text-surface-0 font-medium">{{ option.name }}</span>
3864
+ </div>
3865
+ </ng-template>
3866
+ </p-autocomplete>
3867
+ </div>
3868
+ </div>
3869
+
3870
+ <ng-template #footer>
3871
+ <div class="flex justify-end gap-3 pt-4 border-t border-surface-200 dark:border-surface-700">
3872
+ <p-button label="Cancel" icon="pi pi-times" [outlined]="true" severity="secondary" (onClick)="handleCancel()" styleClass="flex-1" />
3873
+ <p-button [label]="mode() === 'create' ? 'Create Task' : 'Update Task'" [icon]="mode() === 'create' ? 'pi pi-plus' : 'pi pi-check'" (onClick)="handleSave()" styleClass="flex-1" />
3874
+ </div>
3875
+ </ng-template>
3876
+ </p-drawer>
3877
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i4.Drawer, selector: "p-drawer", inputs: ["appendTo", "motionOptions", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i5$2.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i7$2.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i8$2.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i9.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3878
+ }
3879
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TaskDrawerComponent, decorators: [{
3880
+ type: Component,
3881
+ args: [{
3882
+ selector: 'ux-task-drawer',
3883
+ changeDetection: ChangeDetectionStrategy.OnPush,
3884
+ imports: [FormsModule, ButtonModule, DrawerModule, InputTextModule, TextareaModule, SelectModule, DatePickerModule, AutoCompleteModule, DividerModule, AvatarModule],
3885
+ host: { class: 'ux-task-drawer' },
3886
+ template: `
3887
+ <p-drawer [visible]="visible()" position="right" styleClass="w-full! md:w-[420px]!" (onHide)="onHide()" (visibleChange)="visible.set($event)" appendTo="body">
3888
+ <ng-template #header>
3889
+ <div class="flex items-center gap-3">
3890
+ <i class="pi pi-list-check text-xl text-primary-500"></i>
3891
+ <span class="text-surface-900 dark:text-surface-0 font-semibold text-lg">{{ drawerTitle() }}</span>
3892
+ </div>
3893
+ </ng-template>
3894
+
3895
+ <div class="flex flex-col gap-6 p-1">
3896
+ <div class="flex flex-col gap-2">
3897
+ <label for="task-title" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Task Title</label>
3898
+ <input pInputText id="task-title" [(ngModel)]="formData.title" placeholder="Enter task title..." class="w-full" />
3899
+ </div>
3900
+
3901
+ <div class="flex flex-col gap-2">
3902
+ <label for="task-description" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Description</label>
3903
+ <textarea pTextarea id="task-description" [(ngModel)]="formData.description" placeholder="Enter task description..." [rows]="4" class="w-full"></textarea>
3904
+ </div>
3905
+
3906
+ <div class="flex flex-col gap-2">
3907
+ <label for="task-status" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Status</label>
3908
+ <p-select id="task-status" [(ngModel)]="formData.status" [options]="statusOptions" optionLabel="label" optionValue="value" placeholder="Select status" styleClass="w-full" />
3909
+ </div>
3910
+
3911
+ <p-divider styleClass="my-2" />
3912
+
3913
+ <div class="flex flex-col gap-2">
3914
+ <label for="start-date" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Start Date</label>
3915
+ <p-datepicker id="start-date" [(ngModel)]="formData.startDate" dateFormat="dd.mm.yy" placeholder="Select start date" inputStyleClass="w-full" />
3916
+ </div>
3917
+
3918
+ <div class="flex flex-col gap-2">
3919
+ <label for="end-date" class="text-surface-900 dark:text-surface-0 font-medium text-sm">End Date</label>
3920
+ <p-datepicker id="end-date" [(ngModel)]="formData.endDate" dateFormat="dd.mm.yy" placeholder="Select end date" inputStyleClass="w-full" />
3921
+ </div>
3922
+
3923
+ <p-divider styleClass="my-2" />
3924
+
3925
+ <div class="flex flex-col gap-2">
3926
+ <label for="team-members" class="text-surface-900 dark:text-surface-0 font-medium text-sm">Team Members</label>
3927
+ <p-autocomplete
3928
+ id="team-members"
3929
+ [(ngModel)]="formData.members"
3930
+ [suggestions]="filteredMembers"
3931
+ optionLabel="name"
3932
+ [multiple]="true"
3933
+ placeholder="Search team members..."
3934
+ (completeMethod)="filterMembers($event)"
3935
+ styleClass="w-full"
3936
+ >
3937
+ <ng-template #selecteditem let-value>
3938
+ <div class="flex items-center gap-2 bg-surface-50 dark:bg-surface-900 px-2 py-1 rounded">
3939
+ <p-avatar [image]="'demo/images/avatar/' + value.image" shape="circle" styleClass="w-5 h-5 border border-surface-200 dark:border-surface-700" />
3940
+ </div>
3941
+ </ng-template>
3942
+ <ng-template #item let-option>
3943
+ <div class="flex items-center gap-3">
3944
+ <p-avatar [image]="'demo/images/avatar/' + option.image" shape="circle" styleClass="w-8 h-8 border border-surface-200 dark:border-surface-700" />
3945
+ <span class="text-surface-900 dark:text-surface-0 font-medium">{{ option.name }}</span>
3946
+ </div>
3947
+ </ng-template>
3948
+ </p-autocomplete>
3949
+ </div>
3950
+ </div>
3951
+
3952
+ <ng-template #footer>
3953
+ <div class="flex justify-end gap-3 pt-4 border-t border-surface-200 dark:border-surface-700">
3954
+ <p-button label="Cancel" icon="pi pi-times" [outlined]="true" severity="secondary" (onClick)="handleCancel()" styleClass="flex-1" />
3955
+ <p-button [label]="mode() === 'create' ? 'Create Task' : 'Update Task'" [icon]="mode() === 'create' ? 'pi pi-plus' : 'pi pi-check'" (onClick)="handleSave()" styleClass="flex-1" />
3956
+ </div>
3957
+ </ng-template>
3958
+ </p-drawer>
3959
+ `
3960
+ }]
3961
+ }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], task: [{ type: i0.Input, args: [{ isSignal: true, alias: "task", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], availableMembers: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableMembers", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
3962
+
3005
3963
  /*
3006
3964
  * Public API Surface of @unopsitg/ux
3007
3965
  */
@@ -3010,5 +3968,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
3010
3968
  * Generated bundle index. Do not edit.
3011
3969
  */
3012
3970
 
3013
- export { AiCardBgComponent, AiInsightsCardComponent, AppBreadcrumb, AppConfigurator, AppFooter, AppLayout, AppMenu, AppMenuitem, AppRightMenu, AppSearch, AppSidebar, AppTopbar, AuthLayout, BrandContrast, BrandCrisp, BrandSoft, DetailLayoutComponent, DetailTabDirective, LayoutService, MENU_MODEL, SIDEBAR_LOGO, TOPBAR_MOBILE_LOGO, brandPresets, brandPrimitives };
3971
+ export { AiCardBgComponent, AiInsightsCardComponent, AppBreadcrumb, AppConfigurator, AppFooter, AppLayout, AppMenu, AppMenuitem, AppRightMenu, AppSearch, AppSidebar, AppTopbar, AuthLayout, BrandContrast, BrandCrisp, BrandSoft, CompletionStepsComponent, DetailLayoutComponent, DetailTabDirective, DocumentsCardComponent, FooterMainComponent, FooterService, LayoutService, MENU_MODEL, PillTabsComponent, SIDEBAR_LOGO, TOPBAR_MOBILE_LOGO, TaskDrawerComponent, UxSelectComponent, brandPresets, brandPrimitives };
3014
3972
  //# sourceMappingURL=unopsitg-ux.mjs.map