@unopsitg/ux 21.0.2 → 21.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/_main.scss CHANGED
@@ -24,7 +24,7 @@ body {
24
24
  }
25
25
 
26
26
  .app-dark body {
27
- background: linear-gradient(355deg, var(--p-primary-800) 5%, var(--p-surface-900) 60%, var(--p-surface-950) 86%);
27
+ background: linear-gradient(355deg, var(--p-primary-900) 5%, var(--p-surface-900) 60%, var(--p-surface-950) 86%);
28
28
  }
29
29
  }
30
30
 
@@ -499,7 +499,7 @@
499
499
  }
500
500
 
501
501
  @utility body-large {
502
- @apply text-xl text-center text-surface-700 dark:text-surface-200;
502
+ @apply text-xl text-center text-surface-700 dark:text-surface-100;
503
503
  font-family: "Noto Sans", sans-serif;
504
504
  font-feature-settings:
505
505
  "cv09" on,
@@ -513,7 +513,7 @@
513
513
  }
514
514
 
515
515
  @utility body-medium {
516
- @apply text-lg text-center text-surface-700 dark:text-surface-200;
516
+ @apply text-lg text-center text-surface-700 dark:text-surface-100;
517
517
  font-family: "Noto Sans", sans-serif;
518
518
  font-feature-settings:
519
519
  "cv09" on,
@@ -527,7 +527,7 @@
527
527
  }
528
528
 
529
529
  @utility body-small {
530
- @apply text-base text-center text-surface-700 dark:text-surface-200;
530
+ @apply text-base text-center text-surface-700 dark:text-surface-100;
531
531
  font-family: "Noto Sans", sans-serif;
532
532
  font-feature-settings:
533
533
  "cv09" on,
@@ -541,7 +541,7 @@
541
541
  }
542
542
 
543
543
  @utility body-xsmall {
544
- @apply text-sm text-center text-surface-700 dark:text-surface-200;
544
+ @apply text-sm text-center text-surface-700 dark:text-surface-100;
545
545
  font-family: "Noto Sans", sans-serif;
546
546
  font-feature-settings:
547
547
  "cv09" on,
@@ -585,7 +585,7 @@
585
585
  }
586
586
 
587
587
  @utility label-small {
588
- @apply text-surface-700 dark:text-surface-200 font-medium text-sm text-center;
588
+ @apply text-surface-700 dark:text-surface-100 font-medium text-sm text-center;
589
589
  font-family: "Noto Sans", sans-serif;
590
590
  font-feature-settings:
591
591
  "cv11" on,
@@ -600,7 +600,7 @@
600
600
  }
601
601
 
602
602
  @utility label-xsmall {
603
- @apply text-surface-700 dark:text-surface-200 font-medium text-xs text-center;
603
+ @apply text-surface-700 dark:text-surface-100 font-medium text-xs text-center;
604
604
  font-family: "Noto Sans", sans-serif;
605
605
  font-feature-settings:
606
606
  "cv01" on,
@@ -644,7 +644,7 @@
644
644
  }
645
645
 
646
646
  @utility social-button {
647
- @apply w-full py-2 flex items-center justify-center gap-2 rounded-lg border border-surface-200 dark:border-surface-800 hover:bg-surface-100 dark:hover:bg-surface-800 transition-all text-center text-surface-700 dark:text-surface-200 font-medium leading-normal;
647
+ @apply w-full py-2 flex items-center justify-center gap-2 rounded-lg border border-surface-200 dark:border-surface-800 hover:bg-surface-100 dark:hover:bg-surface-800 transition-all text-center text-surface-700 dark:text-surface-100 font-medium leading-normal;
648
648
  font-family: "Noto Sans", sans-serif;
649
649
  font-feature-settings:
650
650
  "cv08" on,
@@ -3,11 +3,11 @@ 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 } 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';
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 } from '@angular/common';
10
+ import { CommonModule, isPlatformBrowser, NgClass } from '@angular/common';
11
11
  import { BehaviorSubject, filter, Subject, takeUntil } from 'rxjs';
12
12
  import * as i5 from '@angular/forms';
13
13
  import { FormsModule } from '@angular/forms';
@@ -47,6 +47,10 @@ import * as i9 from 'primeng/overlaybadge';
47
47
  import { OverlayBadgeModule } from 'primeng/overlaybadge';
48
48
  import * as i3$4 from 'primeng/styleclass';
49
49
  import { StyleClassModule } from 'primeng/styleclass';
50
+ import * as i2$1 from 'primeng/paginator';
51
+ import { PaginatorModule } from 'primeng/paginator';
52
+ import * as i2$2 from 'primeng/tabs';
53
+ import { TabsModule } from 'primeng/tabs';
50
54
 
51
55
  /**
52
56
  * Brand theme presets built on top of PrimeUIX base presets.
@@ -130,12 +134,12 @@ const brandOverrides = {
130
134
  50: '{darkblue.50}',
131
135
  100: '{darkblue.100}',
132
136
  200: '{darkblue.200}',
133
- 300: '{darkblue.200}',
134
- 400: '{darkblue.300}',
135
- 500: '{darkblue.400}',
136
- 600: '{darkblue.500}',
137
- 700: '{darkblue.600}',
138
- 800: '{darkblue.700}',
137
+ 300: '{darkblue.300}',
138
+ 400: '{darkblue.400}',
139
+ 500: '{darkblue.500}',
140
+ 600: '{darkblue.600}',
141
+ 700: '{darkblue.700}',
142
+ 800: '{darkblue.800}',
139
143
  900: '{darkblue.900}',
140
144
  950: '{darkblue.950}'
141
145
  }
@@ -171,11 +175,46 @@ const brandOverrides = {
171
175
  }
172
176
  }
173
177
  },
178
+ tabs: {
179
+ tablist: {
180
+ background: 'transparent'
181
+ },
182
+ tab: {
183
+ background: 'transparent',
184
+ hoverBackground: '{primary.50}',
185
+ hoverColor: '{primary.700}',
186
+ activeBackground: '{primary.100}',
187
+ activeColor: '{primary.900}',
188
+ borderColor: 'transparent',
189
+ activeBorderColor: '{primary.200}',
190
+ padding: '0.5rem 1rem'
191
+ },
192
+ tabpanel: {
193
+ background: 'transparent',
194
+ padding: '0'
195
+ },
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
+ }
206
+ }
207
+ },
174
208
  paginator: {
175
209
  root: {
176
210
  background: 'transparent'
177
211
  }
178
212
  },
213
+ fileupload: {
214
+ root: {
215
+ background: 'transparent'
216
+ }
217
+ },
179
218
  tag: {
180
219
  root: {
181
220
  padding: '0.25rem 0.5rem',
@@ -2584,6 +2623,385 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2584
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"] }]
2585
2624
  }] });
2586
2625
 
2626
+ class AiInsightsCardComponent {
2627
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
2628
+ insights = input.required(...(ngDevMode ? [{ debugName: "insights" }] : []));
2629
+ searchPlaceholder = input('Search AI insights...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : []));
2630
+ actionClick = output();
2631
+ expanded = signal(false, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
2632
+ searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
2633
+ filteredInsights = computed(() => {
2634
+ const query = this.searchQuery().trim().toLowerCase();
2635
+ if (!query)
2636
+ return this.insights();
2637
+ return this.insights().filter(i => i.title.toLowerCase().includes(query) ||
2638
+ i.description.toLowerCase().includes(query));
2639
+ }, ...(ngDevMode ? [{ debugName: "filteredInsights" }] : []));
2640
+ perPage = signal(this.calcPerPage(), ...(ngDevMode ? [{ debugName: "perPage" }] : []));
2641
+ page = signal(0, ...(ngDevMode ? [{ debugName: "page" }] : []));
2642
+ first = computed(() => this.page() * this.perPage(), ...(ngDevMode ? [{ debugName: "first" }] : []));
2643
+ paginatedInsights = computed(() => {
2644
+ const all = this.filteredInsights();
2645
+ return all.slice(this.first(), this.first() + this.perPage());
2646
+ }, ...(ngDevMode ? [{ debugName: "paginatedInsights" }] : []));
2647
+ destroyRef = inject(DestroyRef);
2648
+ ngOnInit() {
2649
+ const onResize = () => this.perPage.set(this.calcPerPage());
2650
+ window.addEventListener('resize', onResize);
2651
+ this.destroyRef.onDestroy(() => window.removeEventListener('resize', onResize));
2652
+ }
2653
+ calcPerPage() {
2654
+ const shellOffset = 12 * 16;
2655
+ const cardChrome = 160 + 72;
2656
+ const insightCardHeight = 150;
2657
+ const available = (typeof window !== 'undefined' ? window.innerHeight : 900) - shellOffset - cardChrome;
2658
+ return Math.max(1, Math.floor(available / insightCardHeight));
2659
+ }
2660
+ 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())">
2665
+ <div class="flex items-center gap-3">
2666
+ <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>
2668
+ </div>
2669
+ <div class="flex flex-col">
2670
+ <h4 class="title-h4 text-left text-deepsea-500 dark:text-surface-0">{{ title() }}</h4>
2671
+ <span class="text-midnight-700 dark:text-surface-100 text-sm font-medium leading-tight">{{ insights().length }} insights available for your review</span>
2672
+ </div>
2673
+ </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>
2677
+ </div>
2678
+
2679
+ <div class="expand-body" [class.expand-body--open]="expanded()">
2680
+ <div class="expand-body__inner">
2681
+ <div class="flex flex-col gap-4 mt-4 flex-1 min-h-0">
2682
+ <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
+ <i class="pi pi-search text-surface-500 dark:text-surface-300 text-sm"></i>
2684
+ <input
2685
+ type="text"
2686
+ [ngModel]="searchQuery()"
2687
+ (ngModelChange)="searchQuery.set($event); page.set(0)"
2688
+ [placeholder]="searchPlaceholder()"
2689
+ class="bg-transparent border-none outline-none flex-1 text-sm font-medium text-deepsea-500 dark:text-surface-0 placeholder:text-surface-700 dark:placeholder:text-surface-300"
2690
+ />
2691
+ </div>
2692
+
2693
+ <div class="flex flex-col gap-3 flex-1 min-h-0 overflow-y-auto overscroll-y-contain pr-0.5">
2694
+ @for (insight of paginatedInsights(); track insight.id) {
2695
+ <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
+ <i class="pi mt-0.5" [ngClass]="[insight.icon, insight.iconColor]"></i>
2697
+ <div class="flex flex-col gap-2 flex-1 min-w-0">
2698
+ <div class="flex flex-col gap-1">
2699
+ <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>
2701
+ </div>
2702
+ <button
2703
+ 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"
2704
+ (click)="actionClick.emit(insight)"
2705
+ >
2706
+ {{ insight.actionLabel }}
2707
+ <i class="pi pi-arrow-right text-xs"></i>
2708
+ </button>
2709
+ </div>
2710
+ </div>
2711
+ }
2712
+ </div>
2713
+
2714
+ <div class="shrink-0 w-full border-t border-white/50 dark:border-surface-700/50 pt-2 mt-1 relative z-[1] bg-transparent">
2715
+ <p-paginator
2716
+ [rows]="perPage()"
2717
+ [totalRecords]="filteredInsights().length"
2718
+ [first]="first()"
2719
+ (onPageChange)="page.set($event.page ?? 0)"
2720
+ [pageLinkSize]="3"
2721
+ styleClass="w-full border-none! bg-transparent!"
2722
+ [pt]="{ root: { class: 'bg-transparent! relative! w-full! justify-center!' } }"
2723
+ />
2724
+ </div>
2725
+ </div>
2726
+ </div>
2727
+ </div>
2728
+ </div>
2729
+ </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 });
2731
+ }
2732
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: AiInsightsCardComponent, decorators: [{
2733
+ type: Component,
2734
+ 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()'
2737
+ }, 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())">
2741
+ <div class="flex items-center gap-3">
2742
+ <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>
2744
+ </div>
2745
+ <div class="flex flex-col">
2746
+ <h4 class="title-h4 text-left text-deepsea-500 dark:text-surface-0">{{ title() }}</h4>
2747
+ <span class="text-midnight-700 dark:text-surface-100 text-sm font-medium leading-tight">{{ insights().length }} insights available for your review</span>
2748
+ </div>
2749
+ </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>
2753
+ </div>
2754
+
2755
+ <div class="expand-body" [class.expand-body--open]="expanded()">
2756
+ <div class="expand-body__inner">
2757
+ <div class="flex flex-col gap-4 mt-4 flex-1 min-h-0">
2758
+ <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
+ <i class="pi pi-search text-surface-500 dark:text-surface-300 text-sm"></i>
2760
+ <input
2761
+ type="text"
2762
+ [ngModel]="searchQuery()"
2763
+ (ngModelChange)="searchQuery.set($event); page.set(0)"
2764
+ [placeholder]="searchPlaceholder()"
2765
+ class="bg-transparent border-none outline-none flex-1 text-sm font-medium text-deepsea-500 dark:text-surface-0 placeholder:text-surface-700 dark:placeholder:text-surface-300"
2766
+ />
2767
+ </div>
2768
+
2769
+ <div class="flex flex-col gap-3 flex-1 min-h-0 overflow-y-auto overscroll-y-contain pr-0.5">
2770
+ @for (insight of paginatedInsights(); track insight.id) {
2771
+ <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
+ <i class="pi mt-0.5" [ngClass]="[insight.icon, insight.iconColor]"></i>
2773
+ <div class="flex flex-col gap-2 flex-1 min-w-0">
2774
+ <div class="flex flex-col gap-1">
2775
+ <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>
2777
+ </div>
2778
+ <button
2779
+ 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"
2780
+ (click)="actionClick.emit(insight)"
2781
+ >
2782
+ {{ insight.actionLabel }}
2783
+ <i class="pi pi-arrow-right text-xs"></i>
2784
+ </button>
2785
+ </div>
2786
+ </div>
2787
+ }
2788
+ </div>
2789
+
2790
+ <div class="shrink-0 w-full border-t border-white/50 dark:border-surface-700/50 pt-2 mt-1 relative z-[1] bg-transparent">
2791
+ <p-paginator
2792
+ [rows]="perPage()"
2793
+ [totalRecords]="filteredInsights().length"
2794
+ [first]="first()"
2795
+ (onPageChange)="page.set($event.page ?? 0)"
2796
+ [pageLinkSize]="3"
2797
+ styleClass="w-full border-none! bg-transparent!"
2798
+ [pt]="{ root: { class: 'bg-transparent! relative! w-full! justify-center!' } }"
2799
+ />
2800
+ </div>
2801
+ </div>
2802
+ </div>
2803
+ </div>
2804
+ </div>
2805
+ </ux-ai-card-bg>
2806
+ `, styles: [":host{display:flex}\n"] }]
2807
+ }], 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
+
2809
+ /**
2810
+ * Structural directive that marks a template as content for a specific tab
2811
+ * inside `<ux-detail-layout>`.
2812
+ *
2813
+ * Usage: `<ng-template uxDetailTab="overview">...content...</ng-template>`
2814
+ */
2815
+ class DetailTabDirective {
2816
+ templateRef;
2817
+ uxDetailTab = input.required(...(ngDevMode ? [{ debugName: "uxDetailTab" }] : []));
2818
+ constructor(templateRef) {
2819
+ this.templateRef = templateRef;
2820
+ }
2821
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DetailTabDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
2822
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.8", type: DetailTabDirective, isStandalone: true, selector: "[uxDetailTab]", inputs: { uxDetailTab: { classPropertyName: "uxDetailTab", publicName: "uxDetailTab", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
2823
+ }
2824
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DetailTabDirective, decorators: [{
2825
+ type: Directive,
2826
+ args: [{ selector: '[uxDetailTab]' }]
2827
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { uxDetailTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "uxDetailTab", required: true }] }] } });
2828
+ /**
2829
+ * Reusable detail-page layout shell: sticky header, tabbed main column,
2830
+ * and a persistent right sidebar (typically for AI insights).
2831
+ *
2832
+ * All styling derives from the active PrimeNG brand preset (BrandSoft / BrandCrisp /
2833
+ * BrandContrast) via `--p-*` CSS variables and Tailwind `surface-*` / `primary-*`
2834
+ * utilities (resolved by `tailwindcss-primeui`). No hardcoded colors.
2835
+ *
2836
+ * ```html
2837
+ * <ux-detail-layout [tabs]="myTabs" [(activeTab)]="currentTab">
2838
+ * <ng-container ux-detail-header>
2839
+ * ...sticky header content...
2840
+ * </ng-container>
2841
+ *
2842
+ * <ng-template uxDetailTab="overview">...tab 1...</ng-template>
2843
+ * <ng-template uxDetailTab="scope">...tab 2...</ng-template>
2844
+ *
2845
+ * <ng-container ux-detail-sidebar>
2846
+ * ...right sidebar (AI card, documents, etc.)...
2847
+ * </ng-container>
2848
+ *
2849
+ * <ng-container ux-detail-footer>
2850
+ * ...audit metadata row...
2851
+ * </ng-container>
2852
+ * </ux-detail-layout>
2853
+ * ```
2854
+ */
2855
+ class DetailLayoutComponent {
2856
+ /** Tab definitions for the main content area. */
2857
+ tabs = input.required(...(ngDevMode ? [{ debugName: "tabs" }] : []));
2858
+ /** Currently active tab value (two-way bindable). */
2859
+ activeTab = model('', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
2860
+ /** True once the scrollable body has been scrolled past the threshold. */
2861
+ scrolled = signal(false, ...(ngDevMode ? [{ debugName: "scrolled" }] : []));
2862
+ /** Tab content templates provided by the consumer. */
2863
+ tabTemplates;
2864
+ getTabTemplate(value) {
2865
+ const match = this.tabTemplates?.find(t => t.uxDetailTab() === value);
2866
+ return match?.templateRef ?? null;
2867
+ }
2868
+ onScroll(event) {
2869
+ const el = event.target;
2870
+ this.scrolled.set(el.scrollTop > 10);
2871
+ }
2872
+ 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)'">
2875
+
2876
+ <!-- Sticky header (projected) -->
2877
+ <div class="ux-dl__header flex-shrink-0 z-10">
2878
+ <div>
2879
+ <ng-content select="[ux-detail-header]" />
2880
+ </div>
2881
+ <div class="ux-dl__header-meta" [class.ux-dl__header-meta--hidden]="scrolled()">
2882
+ <ng-content select="[ux-detail-header-meta]" />
2883
+ </div>
2884
+ </div>
2885
+
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)">
2889
+
2890
+ <!-- Full-width tab bar -->
2891
+ <p-tabs [value]="activeTab()" (valueChange)="activeTab.set($event + '')">
2892
+ <p-tablist>
2893
+ @for (tab of tabs(); track tab.value) {
2894
+ <p-tab [value]="tab.value">
2895
+ @if (tab.icon) {
2896
+ <i [class]="tab.icon" class="mr-2 text-sm"></i>
2897
+ }
2898
+ {{ tab.label }}
2899
+ </p-tab>
2900
+ }
2901
+ </p-tablist>
2902
+
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">
2905
+
2906
+ <!-- Main column: tab panels -->
2907
+ <div class="flex-1 min-w-0 flex flex-col gap-6">
2908
+ <p-tabpanels>
2909
+ @for (tab of tabs(); track tab.value) {
2910
+ <p-tabpanel [value]="tab.value">
2911
+ <div class="flex flex-col gap-6">
2912
+ @if (getTabTemplate(tab.value); as tmpl) {
2913
+ <ng-container [ngTemplateOutlet]="tmpl" />
2914
+ }
2915
+ </div>
2916
+ </p-tabpanel>
2917
+ }
2918
+ </p-tabpanels>
2919
+
2920
+ <!-- Footer below tab content -->
2921
+ <ng-content select="[ux-detail-footer]" />
2922
+ </div>
2923
+
2924
+ <!-- Sidebar -->
2925
+ <aside class="w-full lg:w-[380px] shrink-0 flex flex-col lg:sticky lg:top-4 lg:self-start pb-8">
2926
+ <div class="ux-dl__sidebar-inner w-full">
2927
+ <ng-content select="[ux-detail-sidebar]" />
2928
+ </div>
2929
+ </aside>
2930
+ </div>
2931
+ </p-tabs>
2932
+ </div>
2933
+ </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 });
2935
+ }
2936
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DetailLayoutComponent, decorators: [{
2937
+ 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)'">
2940
+
2941
+ <!-- Sticky header (projected) -->
2942
+ <div class="ux-dl__header flex-shrink-0 z-10">
2943
+ <div>
2944
+ <ng-content select="[ux-detail-header]" />
2945
+ </div>
2946
+ <div class="ux-dl__header-meta" [class.ux-dl__header-meta--hidden]="scrolled()">
2947
+ <ng-content select="[ux-detail-header-meta]" />
2948
+ </div>
2949
+ </div>
2950
+
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)">
2954
+
2955
+ <!-- Full-width tab bar -->
2956
+ <p-tabs [value]="activeTab()" (valueChange)="activeTab.set($event + '')">
2957
+ <p-tablist>
2958
+ @for (tab of tabs(); track tab.value) {
2959
+ <p-tab [value]="tab.value">
2960
+ @if (tab.icon) {
2961
+ <i [class]="tab.icon" class="mr-2 text-sm"></i>
2962
+ }
2963
+ {{ tab.label }}
2964
+ </p-tab>
2965
+ }
2966
+ </p-tablist>
2967
+
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">
2970
+
2971
+ <!-- Main column: tab panels -->
2972
+ <div class="flex-1 min-w-0 flex flex-col gap-6">
2973
+ <p-tabpanels>
2974
+ @for (tab of tabs(); track tab.value) {
2975
+ <p-tabpanel [value]="tab.value">
2976
+ <div class="flex flex-col gap-6">
2977
+ @if (getTabTemplate(tab.value); as tmpl) {
2978
+ <ng-container [ngTemplateOutlet]="tmpl" />
2979
+ }
2980
+ </div>
2981
+ </p-tabpanel>
2982
+ }
2983
+ </p-tabpanels>
2984
+
2985
+ <!-- Footer below tab content -->
2986
+ <ng-content select="[ux-detail-footer]" />
2987
+ </div>
2988
+
2989
+ <!-- Sidebar -->
2990
+ <aside class="w-full lg:w-[380px] shrink-0 flex flex-col lg:sticky lg:top-4 lg:self-start pb-8">
2991
+ <div class="ux-dl__sidebar-inner w-full">
2992
+ <ng-content select="[ux-detail-sidebar]" />
2993
+ </div>
2994
+ </aside>
2995
+ </div>
2996
+ </p-tabs>
2997
+ </div>
2998
+ </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: [{
3001
+ type: ContentChildren,
3002
+ args: [DetailTabDirective]
3003
+ }] } });
3004
+
2587
3005
  /*
2588
3006
  * Public API Surface of @unopsitg/ux
2589
3007
  */
@@ -2592,5 +3010,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
2592
3010
  * Generated bundle index. Do not edit.
2593
3011
  */
2594
3012
 
2595
- export { AiCardBgComponent, AppBreadcrumb, AppConfigurator, AppFooter, AppLayout, AppMenu, AppMenuitem, AppRightMenu, AppSearch, AppSidebar, AppTopbar, AuthLayout, BrandContrast, BrandCrisp, BrandSoft, LayoutService, MENU_MODEL, SIDEBAR_LOGO, TOPBAR_MOBILE_LOGO, brandPresets, brandPrimitives };
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 };
2596
3014
  //# sourceMappingURL=unopsitg-ux.mjs.map