@truenas/ui-components 0.1.2 → 0.1.4
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, ChangeDetectionStrategy, Component, inject, Injectable, viewChild, ChangeDetectorRef, effect, computed, ViewEncapsulation, output, signal, forwardRef,
|
|
2
|
+
import { input, ChangeDetectionStrategy, Component, inject, Injectable, viewChild, ChangeDetectorRef, effect, computed, ViewEncapsulation, Directive, contentChildren, output, signal, forwardRef, ElementRef, ViewContainerRef, contentChild, HostListener, TemplateRef, IterableDiffers, Pipe, model, PLATFORM_ID } from '@angular/core';
|
|
3
3
|
import * as i1$1 from '@angular/common';
|
|
4
4
|
import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
5
5
|
import { mdiCheckCircle, mdiAlertCircle, mdiAlert, mdiInformation, mdiDotsVertical, mdiFolderOpen, mdiLock, mdiLoading, mdiFolderPlus, mdiFolderNetwork, mdiHarddisk, mdiDatabase, mdiFile, mdiFolder } from '@mdi/js';
|
|
@@ -16,6 +16,7 @@ import { TemplatePortal, PortalModule, ComponentPortal } from '@angular/cdk/port
|
|
|
16
16
|
import { CdkMenu, CdkMenuItem, CdkMenuTrigger } from '@angular/cdk/menu';
|
|
17
17
|
import { trigger, state, transition, style, animate } from '@angular/animations';
|
|
18
18
|
import { SPACE, ENTER, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW } from '@angular/cdk/keycodes';
|
|
19
|
+
import { jest } from '@jest/globals';
|
|
19
20
|
import { DataSource } from '@angular/cdk/collections';
|
|
20
21
|
import * as i1$2 from '@angular/cdk/tree';
|
|
21
22
|
import { CdkTree, CdkTreeModule, CdkTreeNode, CDK_TREE_NODE_OUTLET_NODE, CdkTreeNodeOutlet, CdkNestedTreeNode } from '@angular/cdk/tree';
|
|
@@ -608,13 +609,40 @@ class TnIconComponent {
|
|
|
608
609
|
return false; // Disable generic CSS class checking for now
|
|
609
610
|
}
|
|
610
611
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
611
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnIconComponent, isStandalone: true, selector: "tn-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, library: { classPropertyName: "library", publicName: "library", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "svgContainer", first: true, predicate: ["svgContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"tn-icon\"\n role=\"img\"\n [ngClass]=\"'tn-icon--' + size()\"\n [style.color]=\"color()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\">\n \n\n @switch (iconResult.source) {\n <!-- Sprite icons (from generated sprite.svg) -->\n @case ('sprite') {\n <svg\n class=\"tn-icon__sprite\"\n aria-hidden=\"true\">\n <use [attr.href]=\"iconResult.spriteUrl\" />\n </svg>\n }\n\n <!-- SVG content (from third-party libraries or assets) -->\n @case ('svg') {\n <div\n #svgContainer\n class=\"tn-icon__svg\">\n </div>\n }\n\n <!-- CSS class icons (Font Awesome, Material Icons, etc.) -->\n @case ('css') {\n <i\n aria-hidden=\"true\"\n [class]=\"'tn-icon__css ' + iconResult.content\">\n </i>\n }\n\n <!-- Unicode characters -->\n @case ('unicode') {\n <span\n class=\"tn-icon__unicode\"\n aria-hidden=\"true\">{{ iconResult.content }}</span>\n }\n\n <!-- Text abbreviation fallback -->\n @default {\n <span\n class=\"tn-icon__text\"\n aria-hidden=\"true\">{{ iconResult.content }}</span>\n }\n }\n</div>", styles: [".tn-icon{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.tn-icon--xs{width:var(--tn-icon-xs)!important;height:var(--tn-icon-xs)!important;font-size:var(--tn-icon-xs)!important}.tn-icon--sm{width:var(--tn-icon-sm)!important;height:var(--tn-icon-sm)!important;font-size:var(--tn-icon-sm)!important}.tn-icon--md{width:var(--tn-icon-md)!important;height:var(--tn-icon-md)!important;font-size:var(--tn-icon-md)!important}.tn-icon--lg{width:var(--tn-icon-lg)!important;height:var(--tn-icon-lg)!important;font-size:var(--tn-icon-lg)!important}.tn-icon--xl{width:var(--tn-icon-xl)!important;height:var(--tn-icon-xl)!important;font-size:var(--tn-icon-xl)!important}.tn-icon__sprite{width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__svg{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.tn-icon__svg :global(svg){width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__css{font-size:inherit;line-height:1;color:inherit}.tn-icon__unicode{font-size:inherit;line-height:1;color:inherit;text-align:center}.tn-icon__text{font-size:.75em;font-weight:600;line-height:1;color:inherit;text-align:center;opacity:.7}.tn-icon{width:var(--tn-icon-size, var(--tn-icon-md));height:var(--tn-icon-size, var(--tn-icon-md));color:var(--tn-icon-color, currentColor)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
612
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnIconComponent, isStandalone: true, selector: "tn-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, library: { classPropertyName: "library", publicName: "library", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.name": "name()", "attr.library": "library()", "attr.size": "size()", "attr.color": "color()" } }, viewQueries: [{ propertyName: "svgContainer", first: true, predicate: ["svgContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"tn-icon\"\n role=\"img\"\n [ngClass]=\"'tn-icon--' + size()\"\n [style.color]=\"color()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\">\n \n\n @switch (iconResult.source) {\n <!-- Sprite icons (from generated sprite.svg) -->\n @case ('sprite') {\n <svg\n class=\"tn-icon__sprite\"\n aria-hidden=\"true\">\n <use [attr.href]=\"iconResult.spriteUrl\" />\n </svg>\n }\n\n <!-- SVG content (from third-party libraries or assets) -->\n @case ('svg') {\n <div\n #svgContainer\n class=\"tn-icon__svg\">\n </div>\n }\n\n <!-- CSS class icons (Font Awesome, Material Icons, etc.) -->\n @case ('css') {\n <i\n aria-hidden=\"true\"\n [class]=\"'tn-icon__css ' + iconResult.content\">\n </i>\n }\n\n <!-- Unicode characters -->\n @case ('unicode') {\n <span\n class=\"tn-icon__unicode\"\n aria-hidden=\"true\">{{ iconResult.content }}</span>\n }\n\n <!-- Text abbreviation fallback -->\n @default {\n <span\n class=\"tn-icon__text\"\n aria-hidden=\"true\">{{ iconResult.content }}</span>\n }\n }\n</div>", styles: [".tn-icon{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.tn-icon--xs{width:var(--tn-icon-xs)!important;height:var(--tn-icon-xs)!important;font-size:var(--tn-icon-xs)!important}.tn-icon--sm{width:var(--tn-icon-sm)!important;height:var(--tn-icon-sm)!important;font-size:var(--tn-icon-sm)!important}.tn-icon--md{width:var(--tn-icon-md)!important;height:var(--tn-icon-md)!important;font-size:var(--tn-icon-md)!important}.tn-icon--lg{width:var(--tn-icon-lg)!important;height:var(--tn-icon-lg)!important;font-size:var(--tn-icon-lg)!important}.tn-icon--xl{width:var(--tn-icon-xl)!important;height:var(--tn-icon-xl)!important;font-size:var(--tn-icon-xl)!important}.tn-icon__sprite{width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__svg{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.tn-icon__svg :global(svg){width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__css{font-size:inherit;line-height:1;color:inherit}.tn-icon__unicode{font-size:inherit;line-height:1;color:inherit;text-align:center}.tn-icon__text{font-size:.75em;font-weight:600;line-height:1;color:inherit;text-align:center;opacity:.7}.tn-icon{width:var(--tn-icon-size, var(--tn-icon-md));height:var(--tn-icon-size, var(--tn-icon-md));color:var(--tn-icon-color, currentColor)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
612
613
|
}
|
|
613
614
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnIconComponent, decorators: [{
|
|
614
615
|
type: Component,
|
|
615
|
-
args: [{ selector: 'tn-icon', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None,
|
|
616
|
+
args: [{ selector: 'tn-icon', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
617
|
+
'[attr.name]': 'name()',
|
|
618
|
+
'[attr.library]': 'library()',
|
|
619
|
+
'[attr.size]': 'size()',
|
|
620
|
+
'[attr.color]': 'color()'
|
|
621
|
+
}, template: "<div\n class=\"tn-icon\"\n role=\"img\"\n [ngClass]=\"'tn-icon--' + size()\"\n [style.color]=\"color()\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n [attr.title]=\"tooltip()\">\n \n\n @switch (iconResult.source) {\n <!-- Sprite icons (from generated sprite.svg) -->\n @case ('sprite') {\n <svg\n class=\"tn-icon__sprite\"\n aria-hidden=\"true\">\n <use [attr.href]=\"iconResult.spriteUrl\" />\n </svg>\n }\n\n <!-- SVG content (from third-party libraries or assets) -->\n @case ('svg') {\n <div\n #svgContainer\n class=\"tn-icon__svg\">\n </div>\n }\n\n <!-- CSS class icons (Font Awesome, Material Icons, etc.) -->\n @case ('css') {\n <i\n aria-hidden=\"true\"\n [class]=\"'tn-icon__css ' + iconResult.content\">\n </i>\n }\n\n <!-- Unicode characters -->\n @case ('unicode') {\n <span\n class=\"tn-icon__unicode\"\n aria-hidden=\"true\">{{ iconResult.content }}</span>\n }\n\n <!-- Text abbreviation fallback -->\n @default {\n <span\n class=\"tn-icon__text\"\n aria-hidden=\"true\">{{ iconResult.content }}</span>\n }\n }\n</div>", styles: [".tn-icon{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.tn-icon--xs{width:var(--tn-icon-xs)!important;height:var(--tn-icon-xs)!important;font-size:var(--tn-icon-xs)!important}.tn-icon--sm{width:var(--tn-icon-sm)!important;height:var(--tn-icon-sm)!important;font-size:var(--tn-icon-sm)!important}.tn-icon--md{width:var(--tn-icon-md)!important;height:var(--tn-icon-md)!important;font-size:var(--tn-icon-md)!important}.tn-icon--lg{width:var(--tn-icon-lg)!important;height:var(--tn-icon-lg)!important;font-size:var(--tn-icon-lg)!important}.tn-icon--xl{width:var(--tn-icon-xl)!important;height:var(--tn-icon-xl)!important;font-size:var(--tn-icon-xl)!important}.tn-icon__sprite{width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__svg{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.tn-icon__svg :global(svg){width:100%;height:100%;fill:currentColor;color:inherit}.tn-icon__css{font-size:inherit;line-height:1;color:inherit}.tn-icon__unicode{font-size:inherit;line-height:1;color:inherit;text-align:center}.tn-icon__text{font-size:.75em;font-weight:600;line-height:1;color:inherit;text-align:center;opacity:.7}.tn-icon{width:var(--tn-icon-size, var(--tn-icon-md));height:var(--tn-icon-size, var(--tn-icon-md));color:var(--tn-icon-color, currentColor)}\n"] }]
|
|
616
622
|
}], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], library: [{ type: i0.Input, args: [{ isSignal: true, alias: "library", required: false }] }], svgContainer: [{ type: i0.ViewChild, args: ['svgContainer', { isSignal: true }] }] } });
|
|
617
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Directive to mark an element as a banner action.
|
|
626
|
+
* Apply this to any element that should appear in the banner's action area.
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* ```html
|
|
630
|
+
* <tn-banner heading="Error">
|
|
631
|
+
* <button tnBannerAction>Fix Now</button>
|
|
632
|
+
* </tn-banner>
|
|
633
|
+
* ```
|
|
634
|
+
*/
|
|
635
|
+
class TnBannerActionDirective {
|
|
636
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerActionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
637
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.0", type: TnBannerActionDirective, isStandalone: true, selector: "[tnBannerAction]", ngImport: i0 });
|
|
638
|
+
}
|
|
639
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerActionDirective, decorators: [{
|
|
640
|
+
type: Directive,
|
|
641
|
+
args: [{
|
|
642
|
+
selector: '[tnBannerAction]',
|
|
643
|
+
standalone: true,
|
|
644
|
+
}]
|
|
645
|
+
}] });
|
|
618
646
|
const ICON_MAP = {
|
|
619
647
|
'info': 'information',
|
|
620
648
|
'warning': 'alert',
|
|
@@ -623,6 +651,10 @@ const ICON_MAP = {
|
|
|
623
651
|
};
|
|
624
652
|
class TnBannerComponent {
|
|
625
653
|
iconRegistry = inject(TnIconRegistryService);
|
|
654
|
+
/** Query for projected action content */
|
|
655
|
+
actionContent = contentChildren(TnBannerActionDirective, ...(ngDevMode ? [{ debugName: "actionContent" }] : []));
|
|
656
|
+
/** Signal indicating whether action content has been projected */
|
|
657
|
+
hasAction = computed(() => this.actionContent().length > 0, ...(ngDevMode ? [{ debugName: "hasAction" }] : []));
|
|
626
658
|
// Signal-based inputs (modern Angular 19+)
|
|
627
659
|
heading = input.required(...(ngDevMode ? [{ debugName: "heading" }] : []));
|
|
628
660
|
message = input(undefined, ...(ngDevMode ? [{ debugName: "message" }] : []));
|
|
@@ -678,12 +710,12 @@ class TnBannerComponent {
|
|
|
678
710
|
];
|
|
679
711
|
}, ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
680
712
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
681
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnBannerComponent, isStandalone: true, selector: "tn-banner", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-
|
|
713
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnBannerComponent, isStandalone: true, selector: "tn-banner", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "actionContent", predicate: TnBannerActionDirective, isSignal: true }], ngImport: i0, template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:flex;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, #3b82f6);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, #3b82f6)}.tn-banner--warning{border-left-color:var(--tn-warning, #f59e0b);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, #f59e0b)}.tn-banner--error{border-left-color:var(--tn-error, #ef4444);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, #ef4444)}.tn-banner--success{border-left-color:var(--tn-success, #10b981);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, #10b981)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:.875rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;flex-shrink:0;margin-left:auto;padding-left:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library"] }] });
|
|
682
714
|
}
|
|
683
715
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, decorators: [{
|
|
684
716
|
type: Component,
|
|
685
|
-
args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent], template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-
|
|
686
|
-
}], ctorParameters: () => [], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }] } });
|
|
717
|
+
args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent], template: "<div\n aria-live=\"polite\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:flex;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, #3b82f6);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, #3b82f6)}.tn-banner--warning{border-left-color:var(--tn-warning, #f59e0b);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, #f59e0b)}.tn-banner--error{border-left-color:var(--tn-error, #ef4444);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, #ef4444)}.tn-banner--success{border-left-color:var(--tn-success, #10b981);background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, #10b981)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:.875rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;flex-shrink:0;margin-left:auto;padding-left:16px}\n"] }]
|
|
718
|
+
}], ctorParameters: () => [], propDecorators: { actionContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnBannerActionDirective), { isSignal: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }] } });
|
|
687
719
|
|
|
688
720
|
/**
|
|
689
721
|
* Harness for interacting with tn-banner in tests.
|
|
@@ -798,6 +830,98 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
798
830
|
args: [{ selector: 'tn-button', standalone: true, imports: [CommonModule], template: "<button\n type=\"button\"\n [ngClass]=\"classes()\"\n [ngStyle]=\"{ 'background-color': backgroundColor() }\"\n [disabled]=\"disabled()\"\n (click)=\"onClick.emit($event)\"\n>\n {{ label() }}\n</button>\n\n", styles: [":host{display:inline-block;width:fit-content;justify-self:center}.storybook-button{display:inline-block;cursor:pointer;border:0;font-weight:500;line-height:1;font-family:IBM Plex Sans,Helvetica Neue,Helvetica,Arial,sans-serif}.storybook-button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.button-primary{background-color:var(--tn-primary);color:var(--tn-primary-txt)}.button-default{box-shadow:#00000026 0 0 0 1px inset;background-color:var(--tn-btn-default-bg);color:var(--tn-btn-default-txt)}.button-outline-primary{background-color:transparent;border:1px solid var(--tn-primary);color:var(--tn-primary);transition:all .2s ease-in-out}.button-outline-primary:hover{background-color:var(--tn-primary);border:1px solid var(--tn-primary);color:var(--tn-primary-txt)}.button-outline-default{background-color:transparent;border:1px solid var(--tn-lines, #e5e7eb);color:var(--tn-fg1, #000000);transition:all .2s ease-in-out}.button-outline-default:hover{background-color:var(--tn-btn-default-bg);border:1px solid var(--tn-btn-default-bg);color:var(--tn-btn-default-txt);box-shadow:#00000026 0 0 0 1px inset}.button-warn{background-color:var(--tn-red);color:#fff}.button-outline-warn{background-color:transparent;border:1px solid var(--tn-red);color:var(--tn-red);transition:all .2s ease-in-out}.button-outline-warn:hover{background-color:var(--tn-red);border:1px solid var(--tn-red);color:#fff}.storybook-button--small{padding:10px 16px;font-size:12px}.storybook-button--medium{padding:11px 20px;font-size:14px}.storybook-button--large{padding:12px 24px;font-size:16px}\n"] }]
|
|
799
831
|
}], propDecorators: { primary: [{ type: i0.Input, args: [{ isSignal: true, alias: "primary", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], onClick: [{ type: i0.Output, args: ["onClick"] }] } });
|
|
800
832
|
|
|
833
|
+
/**
|
|
834
|
+
* Harness for interacting with tn-button in tests.
|
|
835
|
+
* Provides methods for querying button state and simulating user interactions.
|
|
836
|
+
*
|
|
837
|
+
* @example
|
|
838
|
+
* ```typescript
|
|
839
|
+
* // Find and click a button
|
|
840
|
+
* const saveBtn = await loader.getHarness(TnButtonHarness.with({ label: 'Save' }));
|
|
841
|
+
* await saveBtn.click();
|
|
842
|
+
*
|
|
843
|
+
* // Check if button is disabled
|
|
844
|
+
* const submitBtn = await loader.getHarness(TnButtonHarness.with({ label: 'Submit' }));
|
|
845
|
+
* expect(await submitBtn.isDisabled()).toBe(false);
|
|
846
|
+
*
|
|
847
|
+
* // Find button by regex
|
|
848
|
+
* const cancelBtn = await loader.getHarness(TnButtonHarness.with({ label: /Cancel/i }));
|
|
849
|
+
* ```
|
|
850
|
+
*/
|
|
851
|
+
class TnButtonHarness extends ComponentHarness {
|
|
852
|
+
/**
|
|
853
|
+
* The selector for the host element of a `TnButtonComponent` instance.
|
|
854
|
+
*/
|
|
855
|
+
static hostSelector = 'tn-button';
|
|
856
|
+
_button = this.locatorFor('button');
|
|
857
|
+
/**
|
|
858
|
+
* Gets a `HarnessPredicate` that can be used to search for a button
|
|
859
|
+
* with specific attributes.
|
|
860
|
+
*
|
|
861
|
+
* @param options Options for filtering which button instances are considered a match.
|
|
862
|
+
* @returns A `HarnessPredicate` configured with the given options.
|
|
863
|
+
*
|
|
864
|
+
* @example
|
|
865
|
+
* ```typescript
|
|
866
|
+
* // Find button by exact label
|
|
867
|
+
* const button = await loader.getHarness(TnButtonHarness.with({ label: 'Save' }));
|
|
868
|
+
*
|
|
869
|
+
* // Find button with regex pattern
|
|
870
|
+
* const button = await loader.getHarness(TnButtonHarness.with({ label: /Delete/i }));
|
|
871
|
+
* ```
|
|
872
|
+
*/
|
|
873
|
+
static with(options = {}) {
|
|
874
|
+
return new HarnessPredicate(TnButtonHarness, options)
|
|
875
|
+
.addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Gets the button's label text.
|
|
879
|
+
*
|
|
880
|
+
* @returns Promise resolving to the button's text content.
|
|
881
|
+
*
|
|
882
|
+
* @example
|
|
883
|
+
* ```typescript
|
|
884
|
+
* const button = await loader.getHarness(TnButtonHarness);
|
|
885
|
+
* const label = await button.getLabel();
|
|
886
|
+
* expect(label).toBe('Submit');
|
|
887
|
+
* ```
|
|
888
|
+
*/
|
|
889
|
+
async getLabel() {
|
|
890
|
+
const button = await this._button();
|
|
891
|
+
return (await button.text()).trim();
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Checks whether the button is disabled.
|
|
895
|
+
*
|
|
896
|
+
* @returns Promise resolving to true if the button is disabled.
|
|
897
|
+
*
|
|
898
|
+
* @example
|
|
899
|
+
* ```typescript
|
|
900
|
+
* const button = await loader.getHarness(TnButtonHarness.with({ label: 'Submit' }));
|
|
901
|
+
* expect(await button.isDisabled()).toBe(false);
|
|
902
|
+
* ```
|
|
903
|
+
*/
|
|
904
|
+
async isDisabled() {
|
|
905
|
+
const button = await this._button();
|
|
906
|
+
return (await button.getProperty('disabled')) ?? false;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Clicks the button.
|
|
910
|
+
*
|
|
911
|
+
* @returns Promise that resolves when the click action is complete.
|
|
912
|
+
*
|
|
913
|
+
* @example
|
|
914
|
+
* ```typescript
|
|
915
|
+
* const button = await loader.getHarness(TnButtonHarness.with({ label: 'Save' }));
|
|
916
|
+
* await button.click();
|
|
917
|
+
* ```
|
|
918
|
+
*/
|
|
919
|
+
async click() {
|
|
920
|
+
const button = await this._button();
|
|
921
|
+
return button.click();
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
801
925
|
class TnIconButtonComponent {
|
|
802
926
|
// Button-related inputs
|
|
803
927
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
@@ -2499,6 +2623,110 @@ function libIconMarker(iconName) {
|
|
|
2499
2623
|
return iconName;
|
|
2500
2624
|
}
|
|
2501
2625
|
|
|
2626
|
+
/**
|
|
2627
|
+
* Creates default mock implementation of TnSpriteLoaderService.
|
|
2628
|
+
* All methods return safe default values.
|
|
2629
|
+
*/
|
|
2630
|
+
function createSpriteLoaderMock(overrides) {
|
|
2631
|
+
return {
|
|
2632
|
+
ensureSpriteLoaded: jest.fn(() => Promise.resolve(true)),
|
|
2633
|
+
getIconUrl: jest.fn(() => null),
|
|
2634
|
+
getSafeIconUrl: jest.fn(() => null),
|
|
2635
|
+
isSpriteLoaded: jest.fn(() => true),
|
|
2636
|
+
getSpriteConfig: jest.fn(() => undefined),
|
|
2637
|
+
...overrides,
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Creates default mock implementation of TnIconRegistryService.
|
|
2642
|
+
* Returns a simple SVG for icon resolution so icons render properly in tests
|
|
2643
|
+
* without showing fallback text that could interfere with text content assertions.
|
|
2644
|
+
*/
|
|
2645
|
+
function createIconRegistryMock(spriteLoader, overrides) {
|
|
2646
|
+
return {
|
|
2647
|
+
resolveIcon: jest.fn(() => ({
|
|
2648
|
+
source: 'svg',
|
|
2649
|
+
content: '<svg><path/></svg>',
|
|
2650
|
+
})),
|
|
2651
|
+
getSpriteLoader: jest.fn(() => spriteLoader),
|
|
2652
|
+
registerIcon: jest.fn(),
|
|
2653
|
+
registerIcons: jest.fn(),
|
|
2654
|
+
registerLibrary: jest.fn(),
|
|
2655
|
+
...overrides,
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* Testing utilities for TnIcon components.
|
|
2660
|
+
*
|
|
2661
|
+
* Provides framework-specific mock implementations of icon services to simplify testing
|
|
2662
|
+
* components that use TnIconComponent.
|
|
2663
|
+
*
|
|
2664
|
+
* @example
|
|
2665
|
+
* ```typescript
|
|
2666
|
+
* // Simple usage - "it just works"
|
|
2667
|
+
* await TestBed.configureTestingModule({
|
|
2668
|
+
* imports: [MyComponent],
|
|
2669
|
+
* providers: [
|
|
2670
|
+
* TnIconTesting.jest.providers()
|
|
2671
|
+
* ]
|
|
2672
|
+
* }).compileComponents();
|
|
2673
|
+
* ```
|
|
2674
|
+
*
|
|
2675
|
+
* @example
|
|
2676
|
+
* ```typescript
|
|
2677
|
+
* // Advanced usage - customize mocks
|
|
2678
|
+
* await TestBed.configureTestingModule({
|
|
2679
|
+
* imports: [MyComponent],
|
|
2680
|
+
* providers: [
|
|
2681
|
+
* TnIconTesting.jest.providers({
|
|
2682
|
+
* iconRegistry: {
|
|
2683
|
+
* resolveIcon: jest.fn(() => ({
|
|
2684
|
+
* source: 'sprite',
|
|
2685
|
+
* spriteUrl: '#custom-icon'
|
|
2686
|
+
* }))
|
|
2687
|
+
* }
|
|
2688
|
+
* })
|
|
2689
|
+
* ]
|
|
2690
|
+
* }).compileComponents();
|
|
2691
|
+
* ```
|
|
2692
|
+
*/
|
|
2693
|
+
const TnIconTesting = {
|
|
2694
|
+
/**
|
|
2695
|
+
* Jest-specific testing utilities.
|
|
2696
|
+
*/
|
|
2697
|
+
jest: {
|
|
2698
|
+
/**
|
|
2699
|
+
* Returns Angular providers with mocked icon services.
|
|
2700
|
+
* Creates fresh mock instances on each call to prevent test pollution.
|
|
2701
|
+
*
|
|
2702
|
+
* @param overrides Optional partial mock implementations to customize behavior
|
|
2703
|
+
* @returns Array of providers for TestBed
|
|
2704
|
+
*
|
|
2705
|
+
* @example
|
|
2706
|
+
* ```typescript
|
|
2707
|
+
* // Default mocks
|
|
2708
|
+
* TnIconTesting.jest.providers()
|
|
2709
|
+
*
|
|
2710
|
+
* // Custom sprite loader behavior
|
|
2711
|
+
* TnIconTesting.jest.providers({
|
|
2712
|
+
* spriteLoader: {
|
|
2713
|
+
* getIconUrl: jest.fn(() => '#custom-icon')
|
|
2714
|
+
* }
|
|
2715
|
+
* })
|
|
2716
|
+
* ```
|
|
2717
|
+
*/
|
|
2718
|
+
providers(overrides) {
|
|
2719
|
+
const spriteLoader = createSpriteLoaderMock(overrides?.spriteLoader);
|
|
2720
|
+
const iconRegistry = createIconRegistryMock(spriteLoader, overrides?.iconRegistry);
|
|
2721
|
+
return [
|
|
2722
|
+
{ provide: TnSpriteLoaderService, useValue: spriteLoader },
|
|
2723
|
+
{ provide: TnIconRegistryService, useValue: iconRegistry },
|
|
2724
|
+
];
|
|
2725
|
+
},
|
|
2726
|
+
},
|
|
2727
|
+
// Future: Add vitest, jasmine, or other testing framework support here
|
|
2728
|
+
};
|
|
2729
|
+
|
|
2502
2730
|
/**
|
|
2503
2731
|
* Lucide Icons Integration Helper
|
|
2504
2732
|
*
|
|
@@ -8059,5 +8287,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
8059
8287
|
* Generated bundle index. Do not edit.
|
|
8060
8288
|
*/
|
|
8061
8289
|
|
|
8062
|
-
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_THEME_DEFINITIONS, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCellDefDirective, TnCheckboxComponent, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateRangeInputComponent, TnDialog, TnDialogShellComponent, TnDividerComponent, TnDividerDirective, TnExpansionPanelComponent, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconComponent, TnIconHarness, TnIconRegistryService, TnInputComponent, TnInputDirective, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuComponent, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnSelectComponent, TnSelectionListComponent, TnSlideToggleComponent, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabPanelComponent, TnTableColumnDirective, TnTableComponent, TnTabsComponent, TnTheme, TnThemeService, TnTimeInputComponent, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, libIconMarker, registerLucideIcons, setupLucideIntegration, tnIconMarker };
|
|
8290
|
+
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_THEME_DEFINITIONS, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCellDefDirective, TnCheckboxComponent, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateRangeInputComponent, TnDialog, TnDialogShellComponent, TnDividerComponent, TnDividerDirective, TnExpansionPanelComponent, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuComponent, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnSelectComponent, TnSelectionListComponent, TnSlideToggleComponent, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabPanelComponent, TnTableColumnDirective, TnTableComponent, TnTabsComponent, TnTheme, TnThemeService, TnTimeInputComponent, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, libIconMarker, registerLucideIcons, setupLucideIntegration, tnIconMarker };
|
|
8063
8291
|
//# sourceMappingURL=truenas-ui-components.mjs.map
|