juvo-rafa-library
Version:
A comprehensive Angular component library featuring real-world components and validators extracted from the Juvo Rafa backoffice application. Now with improved select components and bug fixes.
1 lines • 111 kB
Source Map (JSON)
{"version":3,"file":"juvo-rafa-library.mjs","sources":["../../../projects/ui-components/src/lib/juvo-button-action/juvo-button-action.component.ts","../../../projects/ui-components/src/lib/juvo-button-action/juvo-button-action.component.html","../../../projects/ui-components/src/lib/juvo-loading/juvo-loading.component.ts","../../../projects/ui-components/src/lib/juvo-loading/juvo-loading.component.html","../../../projects/ui-components/src/lib/juvo-screen-loading/juvo-screen-loading.component.ts","../../../projects/ui-components/src/lib/juvo-screen-loading/juvo-screen-loading.component.html","../../../projects/ui-components/src/lib/juvo-input/juvo-input.component.ts","../../../projects/ui-components/src/lib/juvo-input/juvo-input.component.html","../../../projects/ui-components/src/lib/juvo-list/juvo-list.component.ts","../../../projects/ui-components/src/lib/juvo-list/juvo-list.component.html","../../../projects/ui-components/src/lib/juvo-table/juvo-table.component.ts","../../../projects/ui-components/src/lib/juvo-table/juvo-table.component.html","../../../projects/ui-components/src/lib/juvo-tab-menu/juvo-tab-menu.component.ts","../../../projects/ui-components/src/lib/juvo-tab-menu/juvo-tab-menu.component.html","../../../projects/ui-components/src/lib/juvo-notification/juvo-notification.component.ts","../../../projects/ui-components/src/lib/juvo-notification/juvo-notification.component.html","../../../projects/ui-components/src/lib/juvo-confirmation-dialog/juvo-confirmation-dialog.component.ts","../../../projects/ui-components/src/lib/juvo-confirmation-dialog/juvo-confirmation-dialog.component.html","../../../projects/ui-components/src/lib/juvo-image-picker/juvo-image-picker.component.ts","../../../projects/ui-components/src/lib/juvo-image-picker/juvo-image-picker.component.html","../../../projects/ui-components/src/lib/validators/ip-address.validator.ts","../../../projects/ui-components/src/lib/validators/mac-address.validator.ts","../../../projects/ui-components/src/lib/validators/port.validator.ts","../../../projects/ui-components/src/lib/validators/serial-port.validator.ts","../../../projects/ui-components/src/lib/validators/index.ts","../../../projects/ui-components/src/public-api.ts","../../../projects/ui-components/src/juvo-rafa-library.ts"],"sourcesContent":["import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Observable } from 'rxjs';\n\n/**\n * Configuration interface for JuvoButtonActionComponent\n * \n * @interface ButtonActionConfig\n * @since 2.0.0\n */\nexport interface ButtonActionConfig {\n /** Show Create button */\n buttonCreate?: boolean;\n /** Show Create and Copy button */\n buttonCreateCopy?: boolean;\n /** Show Save and Copy button */\n buttonSaveCopy?: boolean;\n /** Show Save button */\n buttonSave?: boolean;\n /** Disable all action buttons */\n disable?: boolean;\n /** Custom text for Save button */\n saveText?: string;\n /** Button style: true for solid, false for outlined */\n buttonType?: boolean;\n}\n\n/**\n * Action Button Group Component\n * \n * @description\n * A reusable button group component commonly used in forms and data entry screens.\n * Provides standard actions like Save, Create, Create Copy, Save Copy, and Cancel.\n * Originally designed for backoffice applications where consistent form actions are needed.\n * \n * @example\n * ```html\n * <!-- Basic usage -->\n * <juvo-button-action\n * [buttonSave]=\"true\"\n * [buttonCreate]=\"true\"\n * (actionSaveTriggered)=\"onSave()\"\n * (actionCreateTriggered)=\"onCreate()\"\n * (actionCancelTriggered)=\"onCancel()\">\n * </juvo-button-action>\n * \n * <!-- Advanced usage with loading state -->\n * <juvo-button-action\n * [loading$]=\"loadingObservable$\"\n * [buttonSave]=\"true\"\n * [buttonCreateCopy]=\"true\"\n * [buttonType]=\"true\"\n * [disable]=\"!form.valid\"\n * saveText=\"Update Record\"\n * (actionSaveTriggered)=\"onUpdate()\"\n * (actionCreateCopyTriggered)=\"onDuplicate()\">\n * </juvo-button-action>\n * ```\n * \n * @selector juvo-button-action\n * @since 2.0.0\n * @author Juvo Rafa Team\n */\n@Component({\n selector: 'juvo-button-action',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './juvo-button-action.component.html',\n styleUrl: './juvo-button-action.component.css'\n})\nexport class JuvoButtonActionComponent {\n /** Observable for loading state of buttons */\n @Input() loading$?: Observable<boolean>;\n \n /** Configuration object for button visibility and behavior */\n @Input() config: ButtonActionConfig = {};\n \n /** Show Create button */\n @Input() buttonCreate?: boolean;\n \n /** Show Create and Copy button */\n @Input() buttonCreateCopy?: boolean;\n \n /** Show Save and Copy button */\n @Input() buttonSaveCopy?: boolean;\n \n /** Show Save button */\n @Input() buttonSave?: boolean;\n \n /** Disable all action buttons */\n @Input() disable: boolean = false;\n \n /** Custom text for Save button @default \"Save\" */\n @Input() saveText: string = \"Save\";\n \n /** Button style: true for solid, false for outlined @default false */\n @Input() buttonType: boolean = false;\n \n /** Emitted when Save button is clicked */\n @Output() actionSaveTriggered = new EventEmitter<void>();\n \n /** Emitted when Create button is clicked */\n @Output() actionCreateTriggered = new EventEmitter<void>();\n \n /** Emitted when Create and Copy button is clicked */\n @Output() actionCreateCopyTriggered = new EventEmitter<void>();\n \n /** Emitted when Save and Copy button is clicked */\n @Output() actionSaveCopyTriggered = new EventEmitter<void>();\n \n /** Emitted when Cancel button is clicked */\n @Output() actionCancelTriggered = new EventEmitter<void>();\n\n /**\n * Gets the current loading state\n * @returns true if loading, false otherwise\n */\n get isLoading(): boolean {\n return this.loading$ ? false : false; // Simplified for now\n }\n\n /**\n * Handles Save button click\n * @emits actionSaveTriggered\n */\n onSave(): void {\n this.actionSaveTriggered.emit();\n }\n\n /**\n * Handles Create button click\n * @emits actionCreateTriggered\n */\n onCreate(): void {\n this.actionCreateTriggered.emit();\n }\n\n /**\n * Handles Create and Copy button click\n * @emits actionCreateCopyTriggered\n */\n onCreateCopy(): void {\n this.actionCreateCopyTriggered.emit();\n }\n\n /**\n * Handles Save and Copy button click\n * @emits actionSaveCopyTriggered\n */\n onSaveCopy(): void {\n this.actionSaveCopyTriggered.emit();\n }\n\n /**\n * Handles Cancel button click\n * @emits actionCancelTriggered\n */\n onCancel(): void {\n this.actionCancelTriggered.emit();\n }\n} ","<div class=\"juvo-button-action\">\n <button\n type=\"button\"\n class=\"btn btn-outlined\"\n (click)=\"onCancel()\">\n Cancel\n </button>\n\n <button\n *ngIf=\"buttonCreateCopy\"\n type=\"button\"\n class=\"btn btn-outlined\"\n [disabled]=\"isLoading\"\n (click)=\"onCreateCopy()\">\n <span *ngIf=\"isLoading\" class=\"spinner\"></span>\n Create and copy\n </button>\n\n <button\n *ngIf=\"buttonSaveCopy\"\n type=\"button\"\n class=\"btn btn-outlined\"\n [disabled]=\"isLoading\"\n (click)=\"onSaveCopy()\">\n <span *ngIf=\"isLoading\" class=\"spinner\"></span>\n Save and copy\n </button>\n\n <button\n *ngIf=\"buttonCreate\"\n type=\"button\"\n class=\"btn btn-outlined\"\n [disabled]=\"disable || isLoading\"\n (click)=\"onCreate()\">\n <span *ngIf=\"isLoading\" class=\"spinner\"></span>\n Create\n </button>\n \n <button\n *ngIf=\"buttonSave\"\n type=\"button\"\n [disabled]=\"disable || isLoading\"\n [class]=\"buttonType ? 'btn btn-primary' : 'btn btn-outlined'\"\n (click)=\"onSave()\">\n <span *ngIf=\"isLoading\" class=\"spinner\"></span>\n {{ saveText }}\n </button>\n</div> ","import { Component, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/**\n * Loading size options\n * @type LoadingSize\n * @since 2.0.0\n */\nexport type LoadingSize = 'small' | 'medium' | 'large';\n\n/**\n * Loading Component\n * \n * @description\n * A flexible loading indicator component that can be used inline or as an overlay.\n * Originally designed for backoffice applications to provide consistent loading states.\n * Supports different sizes, custom colors, and overlay modes.\n * \n * @example\n * ```html\n * <!-- Inline loading -->\n * <juvo-loading \n * message=\"Loading data...\" \n * size=\"medium\" \n * [overlay]=\"false\">\n * </juvo-loading>\n * \n * <!-- Overlay loading -->\n * <juvo-loading \n * *ngIf=\"isLoading\"\n * message=\"Please wait...\" \n * size=\"large\" \n * [overlay]=\"true\">\n * </juvo-loading>\n * \n * <!-- Custom styled loading -->\n * <juvo-loading \n * message=\"Processing...\" \n * size=\"small\" \n * color=\"#10b981\" \n * [transparent]=\"true\">\n * </juvo-loading>\n * ```\n * \n * @selector juvo-loading\n * @since 2.0.0\n * @author Juvo Rafa Team\n */\n@Component({\n selector: 'juvo-loading',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './juvo-loading.component.html',\n styleUrl: './juvo-loading.component.css'\n})\nexport class JuvoLoadingComponent {\n /** Loading message to display @default \"Loading...\" */\n @Input() message: string = 'Loading...';\n \n /** Size of the loading spinner @default \"medium\" */\n @Input() size: LoadingSize = 'medium';\n \n /** Color of the spinner @default \"#3b82f6\" */\n @Input() color: string = '#3b82f6';\n \n /** Whether to display as overlay covering the entire screen @default true */\n @Input() overlay: boolean = true;\n \n /** Whether the overlay background should be transparent @default false */\n @Input() transparent: boolean = false;\n\n /**\n * Gets the CSS class for the spinner size\n * @returns CSS class name for the current size\n */\n get sizeClass(): string {\n return `loading-${this.size}`;\n }\n\n /**\n * Gets the CSS classes for the container\n * @returns Combined CSS classes for the container\n */\n get containerClass(): string {\n let classes = 'juvo-loading';\n if (this.overlay) classes += ' overlay';\n if (this.transparent) classes += ' transparent';\n return classes;\n }\n} ","<div [class]=\"containerClass\">\n <div class=\"loading-content\">\n <div [class]=\"sizeClass\" class=\"loading-spinner\">\n <div class=\"spinner-circle\" [style.border-top-color]=\"color\"></div>\n </div>\n <p class=\"loading-message\" *ngIf=\"message\">{{ message }}</p>\n </div>\n</div> ","import { Component, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Observable, combineLatest, map } from 'rxjs';\n\n/**\n * Screen Loading Component\n * \n * @description\n * A screen-wide loading overlay component for blocking UI interactions during operations.\n * Originally designed for backoffice applications to handle multiple loading states.\n * Can combine multiple observables to show loading when any operation is in progress.\n * \n * @example\n * ```html\n * <!-- Single loading state -->\n * <juvo-screen-loading\n * [loading$]=\"dataLoading$\"\n * message=\"Loading data...\">\n * </juvo-screen-loading>\n * \n * <!-- Multiple loading states -->\n * <juvo-screen-loading\n * [loadingList$]=\"[saveLoading$, deleteLoading$, updateLoading$]\"\n * message=\"Processing operations...\"\n * [backdrop]=\"true\">\n * </juvo-screen-loading>\n * \n * <!-- Custom styled loading -->\n * <juvo-screen-loading\n * [loading$]=\"criticalOperation$\"\n * message=\"Critical operation in progress. Please do not refresh the page.\"\n * color=\"#dc2626\"\n * size=\"large\">\n * </juvo-screen-loading>\n * ```\n * \n * @selector juvo-screen-loading\n * @since 2.1.0\n * @author Juvo Rafa Team\n */\n@Component({\n selector: 'juvo-screen-loading',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './juvo-screen-loading.component.html',\n styleUrl: './juvo-screen-loading.component.css'\n})\nexport class JuvoScreenLoadingComponent {\n /** Single loading observable */\n @Input() loading$?: Observable<boolean>;\n \n /** Array of loading observables to combine */\n @Input() loadingList$: Observable<boolean>[] = [];\n \n /** Loading message to display @default \"Loading...\" */\n @Input() message: string = 'Loading...';\n \n /** Size of the loading spinner @default \"large\" */\n @Input() size: 'small' | 'medium' | 'large' = 'large';\n \n /** Color of the spinner @default \"#3b82f6\" */\n @Input() color: string = '#3b82f6';\n \n /** Whether to show backdrop @default true */\n @Input() backdrop: boolean = true;\n \n /** Backdrop opacity @default 0.8 */\n @Input() backdropOpacity: number = 0.8;\n \n /** Whether to show the loading message @default true */\n @Input() showMessage: boolean = true;\n\n /**\n * Gets the combined loading state from all sources\n * @returns Observable<boolean> indicating if any loading is in progress\n */\n get combinedLoading$(): Observable<boolean> {\n const observables: Observable<boolean>[] = [];\n \n if (this.loading$) {\n observables.push(this.loading$);\n }\n \n if (this.loadingList$ && this.loadingList$.length > 0) {\n observables.push(...this.loadingList$);\n }\n \n if (observables.length === 0) {\n return new Observable(subscriber => subscriber.next(false));\n }\n \n if (observables.length === 1) {\n return observables[0];\n }\n \n return combineLatest(observables).pipe(\n map(loadingStates => loadingStates.some(loading => loading))\n );\n }\n\n /**\n * Gets the CSS class for the spinner size\n * @returns CSS class name for the current size\n */\n get sizeClass(): string {\n return `spinner-${this.size}`;\n }\n\n /**\n * Gets the backdrop style\n * @returns CSS style object for backdrop\n */\n get backdropStyle(): any {\n return {\n 'background-color': `rgba(255, 255, 255, ${this.backdropOpacity})`\n };\n }\n} ","<div class=\"screen-loading-overlay\" \n *ngIf=\"combinedLoading$ | async\"\n [class.with-backdrop]=\"backdrop\"\n [ngStyle]=\"backdrop ? backdropStyle : null\">\n \n <div class=\"screen-loading-content\">\n <div class=\"spinner-container\">\n <div [class]=\"sizeClass\" \n class=\"loading-spinner\">\n <div class=\"spinner-circle\" \n [style.border-top-color]=\"color\"\n [style.border-right-color]=\"color\">\n </div>\n </div>\n </div>\n \n <div class=\"loading-message\" \n *ngIf=\"showMessage && message\">\n {{ message }}\n </div>\n </div>\n</div> ","import { Component, Input, Output, EventEmitter, forwardRef } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR, ReactiveFormsModule, FormsModule } from '@angular/forms';\n\n/**\n * Input types supported by JuvoInputComponent\n * @type InputType\n * @since 2.1.0\n */\nexport type InputType = 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search' | 'textarea' | 'select';\n\n/**\n * Input sizes\n * @type InputSize\n * @since 2.1.0\n */\nexport type InputSize = 'small' | 'medium' | 'large';\n\n/**\n * Select option interface\n * @interface SelectOption\n * @since 2.1.0\n */\nexport interface SelectOption {\n label: string;\n value: any;\n disabled?: boolean;\n}\n\n/**\n * Input Component\n * \n * @description\n * A comprehensive input component supporting various input types and form integration.\n * Includes support for text, email, password, number, textarea, and select inputs.\n * Originally designed for backoffice applications with extensive form requirements.\n * \n * @example\n * ```html\n * <!-- Basic text input -->\n * <juvo-input\n * label=\"Full Name\"\n * placeholder=\"Enter your full name\"\n * [(ngModel)]=\"name\">\n * </juvo-input>\n * \n * <!-- Email input with validation -->\n * <juvo-input\n * type=\"email\"\n * label=\"Email Address\"\n * placeholder=\"user@example.com\"\n * [required]=\"true\"\n * [error]=\"emailError\"\n * [(ngModel)]=\"email\">\n * </juvo-input>\n * \n * <!-- Select dropdown -->\n * <juvo-input\n * type=\"select\"\n * label=\"Country\"\n * [options]=\"countryOptions\"\n * [(ngModel)]=\"selectedCountry\">\n * </juvo-input>\n * \n * <!-- Textarea -->\n * <juvo-input\n * type=\"textarea\"\n * label=\"Description\"\n * [rows]=\"4\"\n * placeholder=\"Enter description...\"\n * [(ngModel)]=\"description\">\n * </juvo-input>\n * ```\n * \n * @selector juvo-input\n * @since 2.1.0\n * @author Juvo Rafa Team\n */\n@Component({\n selector: 'juvo-input',\n standalone: true,\n imports: [CommonModule, ReactiveFormsModule, FormsModule],\n templateUrl: './juvo-input.component.html',\n styleUrl: './juvo-input.component.css',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => JuvoInputComponent),\n multi: true\n }\n ]\n})\nexport class JuvoInputComponent implements ControlValueAccessor {\n /** Input label */\n @Input() label?: string;\n \n /** Input placeholder text */\n @Input() placeholder: string = '';\n \n /** Input type @default \"text\" */\n @Input() type: InputType = 'text';\n \n /** Input size @default \"medium\" */\n @Input() size: InputSize = 'medium';\n \n /** Whether input is disabled @default false */\n @Input() disabled: boolean = false;\n \n /** Whether input is readonly @default false */\n @Input() readonly: boolean = false;\n \n /** Whether input is required @default false */\n @Input() required: boolean = false;\n \n /** Error message to display */\n @Input() error?: string;\n \n /** Hint text to display */\n @Input() hint?: string;\n \n /** Maximum length for text inputs */\n @Input() maxLength?: number;\n \n /** Minimum value for number inputs */\n @Input() min?: number;\n \n /** Maximum value for number inputs */\n @Input() max?: number;\n \n /** Step value for number inputs */\n @Input() step?: number;\n \n /** Number of rows for textarea @default 3 */\n @Input() rows: number = 3;\n \n /** Options for select input */\n @Input() options: SelectOption[] = [];\n \n /** Prefix text or icon */\n @Input() prefix?: string;\n \n /** Suffix text or icon */\n @Input() suffix?: string;\n \n /** Whether to show character counter @default false */\n @Input() showCounter: boolean = false;\n \n /** Emitted when value changes */\n @Output() valueChange = new EventEmitter<any>();\n \n /** Emitted when input receives focus */\n @Output() onFocus = new EventEmitter<void>();\n \n /** Emitted when input loses focus */\n @Output() onBlur = new EventEmitter<void>();\n\n private _value: any = '';\n private _onChange = (value: any) => {};\n private _onTouched = () => {};\n\n get value(): any {\n return this._value;\n }\n\n set value(val: any) {\n if (val !== this._value) {\n this._value = val;\n this._onChange(val);\n this.valueChange.emit(val);\n }\n }\n\n /**\n * Gets CSS classes for the input container\n * @returns Combined CSS classes\n */\n get containerClasses(): string {\n let classes = `input-container input-${this.size}`;\n if (this.disabled) classes += ' disabled';\n if (this.error) classes += ' error';\n if (this.readonly) classes += ' readonly';\n return classes;\n }\n\n /**\n * Gets CSS classes for the input element\n * @returns Combined CSS classes\n */\n get inputClasses(): string {\n let classes = 'input-element';\n if (this.prefix) classes += ' has-prefix';\n if (this.suffix) classes += ' has-suffix';\n return classes;\n }\n\n /**\n * Handles input value changes\n */\n onInput(event: Event): void {\n const target = event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n this.value = target.value;\n }\n\n /**\n * Handles input focus\n */\n handleFocus(): void {\n this.onFocus.emit();\n }\n\n /**\n * Handles input blur\n */\n handleBlur(): void {\n this._onTouched();\n this.onBlur.emit();\n }\n\n /**\n * Gets the character count for display\n * @returns Current character count\n */\n get characterCount(): number {\n return this.value ? this.value.toString().length : 0;\n }\n\n // ControlValueAccessor implementation\n writeValue(value: any): void {\n this._value = value || '';\n }\n\n registerOnChange(fn: (value: any) => void): void {\n this._onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this._onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.disabled = isDisabled;\n }\n} ","<div [class]=\"containerClasses\">\n <!-- Label -->\n <label class=\"input-label\" *ngIf=\"label\">\n {{ label }}\n <span class=\"required-indicator\" *ngIf=\"required\">*</span>\n </label>\n\n <!-- Input wrapper -->\n <div class=\"input-wrapper\">\n <!-- Prefix -->\n <span class=\"input-prefix\" *ngIf=\"prefix\">{{ prefix }}</span>\n\n <!-- Text Input -->\n <input\n *ngIf=\"type !== 'textarea' && type !== 'select'\"\n [class]=\"inputClasses\"\n [type]=\"type\"\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled\"\n [readonly]=\"readonly\"\n [required]=\"required\"\n [attr.maxlength]=\"maxLength\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [value]=\"value\"\n (input)=\"onInput($event)\"\n (focus)=\"handleFocus()\"\n (blur)=\"handleBlur()\"\n />\n\n <!-- Textarea -->\n <textarea\n *ngIf=\"type === 'textarea'\"\n [class]=\"inputClasses\"\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled\"\n [readonly]=\"readonly\"\n [required]=\"required\"\n [attr.maxlength]=\"maxLength\"\n [rows]=\"rows\"\n [value]=\"value\"\n (input)=\"onInput($event)\"\n (focus)=\"handleFocus()\"\n (blur)=\"handleBlur()\">\n </textarea>\n\n <!-- Select -->\n <select\n *ngIf=\"type === 'select'\"\n [class]=\"inputClasses\"\n [disabled]=\"disabled\"\n [required]=\"required\"\n [value]=\"value\"\n (change)=\"onInput($event)\"\n (focus)=\"handleFocus()\"\n (blur)=\"handleBlur()\">\n <option value=\"\" disabled *ngIf=\"placeholder\">{{ placeholder }}</option>\n <option \n *ngFor=\"let option of options\"\n [value]=\"option.value\"\n [disabled]=\"option.disabled\">\n {{ option.label }}\n </option>\n </select>\n\n <!-- Suffix -->\n <span class=\"input-suffix\" *ngIf=\"suffix\">{{ suffix }}</span>\n </div>\n\n <!-- Footer -->\n <div class=\"input-footer\" *ngIf=\"error || hint || (showCounter && maxLength)\">\n <!-- Error message -->\n <div class=\"input-error\" *ngIf=\"error\">\n {{ error }}\n </div>\n \n <!-- Hint text -->\n <div class=\"input-hint\" *ngIf=\"hint && !error\">\n {{ hint }}\n </div>\n \n <!-- Character counter -->\n <div class=\"input-counter\" *ngIf=\"showCounter && maxLength && !error\">\n {{ characterCount }}/{{ maxLength }}\n </div>\n </div>\n</div> ","import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Observable, of } from 'rxjs';\n\n/**\n * List column configuration interface\n * @interface ListColumn\n * @since 2.1.0\n */\nexport interface ListColumn {\n key: string;\n label: string;\n sortable?: boolean;\n width?: string;\n type?: 'text' | 'number' | 'date' | 'boolean' | 'custom';\n}\n\n/**\n * List Component\n * \n * @description\n * A simple list component for displaying tabular data with basic functionality.\n * Originally designed for backoffice applications requiring data presentation.\n * Supports sorting, custom columns, and item selection.\n * \n * @example\n * ```html\n * <!-- Basic list -->\n * <juvo-list\n * [data$]=\"listData$\"\n * [columns]=\"[\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email' },\n * { key: 'status', label: 'Status', type: 'boolean' }\n * ]\"\n * (itemSelected)=\"onItemSelected($event)\">\n * </juvo-list>\n * \n * <!-- List with loading state -->\n * <juvo-list\n * [data$]=\"userData$\"\n * [columns]=\"userColumns\"\n * [loading$]=\"userLoading$\"\n * [selectable]=\"true\"\n * (itemSelected)=\"handleSelection($event)\">\n * </juvo-list>\n * ```\n * \n * @selector juvo-list\n * @since 2.1.0\n * @author Juvo Rafa Team\n */\n@Component({\n selector: 'juvo-list',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './juvo-list.component.html',\n styleUrl: './juvo-list.component.css'\n})\nexport class JuvoListComponent {\n /** Observable data source */\n @Input() data$: Observable<any[]> = of([]);\n \n /** Column configuration */\n @Input() columns: ListColumn[] = [];\n \n /** Loading state observable */\n @Input() loading$: Observable<boolean> = of(false);\n \n /** Whether items are selectable @default false */\n @Input() selectable: boolean = false;\n \n /** Whether to show header @default true */\n @Input() showHeader: boolean = true;\n \n /** Empty state message */\n @Input() emptyMessage: string = 'No data available';\n \n /** Loading message */\n @Input() loadingMessage: string = 'Loading...';\n \n /** List size variant @default \"medium\" */\n @Input() size: 'small' | 'medium' | 'large' = 'medium';\n \n /** Whether to show borders @default true */\n @Input() bordered: boolean = true;\n \n /** Whether to show striped rows @default true */\n @Input() striped: boolean = true;\n \n /** Emitted when an item is selected */\n @Output() itemSelected = new EventEmitter<any>();\n \n /** Emitted when an item is clicked */\n @Output() itemClicked = new EventEmitter<any>();\n \n /** Emitted when column header is clicked for sorting */\n @Output() columnSort = new EventEmitter<{ column: ListColumn, direction: 'asc' | 'desc' }>();\n\n selectedItems: any[] = [];\n sortColumn?: string;\n sortDirection: 'asc' | 'desc' = 'asc';\n\n /**\n * Gets CSS classes for the list container\n * @returns Combined CSS classes\n */\n get containerClasses(): string {\n let classes = `juvo-list juvo-list-${this.size}`;\n if (this.bordered) classes += ' bordered';\n if (this.striped) classes += ' striped';\n return classes;\n }\n\n /**\n * Handles item selection\n */\n onItemSelect(item: any): void {\n if (!this.selectable) return;\n \n const index = this.selectedItems.findIndex(selected => selected === item);\n if (index > -1) {\n this.selectedItems.splice(index, 1);\n } else {\n this.selectedItems.push(item);\n }\n \n this.itemSelected.emit(item);\n }\n\n /**\n * Handles item click\n */\n onItemClick(item: any): void {\n this.itemClicked.emit(item);\n }\n\n /**\n * Handles column header click for sorting\n */\n onColumnHeaderClick(column: ListColumn): void {\n if (!column.sortable) return;\n \n if (this.sortColumn === column.key) {\n this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';\n } else {\n this.sortColumn = column.key;\n this.sortDirection = 'asc';\n }\n \n this.columnSort.emit({ column, direction: this.sortDirection });\n }\n\n /**\n * Checks if an item is selected\n */\n isSelected(item: any): boolean {\n return this.selectedItems.includes(item);\n }\n\n /**\n * Gets the display value for a cell\n */\n getCellValue(item: any, column: ListColumn): string {\n const value = item[column.key];\n \n switch (column.type) {\n case 'boolean':\n return value ? '✅' : '❌';\n case 'date':\n return value ? new Date(value).toLocaleDateString() : '';\n case 'number':\n return typeof value === 'number' ? value.toLocaleString() : '';\n default:\n return value?.toString() || '';\n }\n }\n\n /**\n * Gets CSS classes for column header\n */\n getHeaderClasses(column: ListColumn): string {\n let classes = 'list-header-cell';\n if (column.sortable) classes += ' sortable';\n if (this.sortColumn === column.key) classes += ` sorted-${this.sortDirection}`;\n return classes;\n }\n\n /**\n * Track by function for ngFor performance\n */\n trackByFn(index: number, item: any): any {\n return item.id || index;\n }\n} ","<div [class]=\"containerClasses\">\n <!-- Loading State -->\n <div class=\"list-loading\" *ngIf=\"loading$ | async\">\n <div class=\"loading-spinner\"></div>\n <div class=\"loading-text\">{{ loadingMessage }}</div>\n </div>\n\n <!-- List Content -->\n <div class=\"list-content\" *ngIf=\"!(loading$ | async)\">\n <!-- Header -->\n <div class=\"list-header\" *ngIf=\"showHeader && columns.length > 0\">\n <div class=\"list-header-row\">\n <div \n *ngIf=\"selectable\" \n class=\"list-header-cell checkbox-cell\">\n <!-- Checkbox for select all could go here -->\n </div>\n <div\n *ngFor=\"let column of columns\"\n [class]=\"getHeaderClasses(column)\"\n [style.width]=\"column.width\"\n (click)=\"onColumnHeaderClick(column)\">\n {{ column.label }}\n <span class=\"sort-indicator\" *ngIf=\"column.sortable && sortColumn === column.key\">\n {{ sortDirection === 'asc' ? '↑' : '↓' }}\n </span>\n </div>\n </div>\n </div>\n\n <!-- Body -->\n <div class=\"list-body\">\n <!-- Data Rows -->\n <div \n class=\"list-row\"\n *ngFor=\"let item of data$ | async; trackBy: trackByFn\"\n [class.selected]=\"isSelected(item)\"\n (click)=\"onItemClick(item)\">\n \n <!-- Selection checkbox -->\n <div \n *ngIf=\"selectable\" \n class=\"list-cell checkbox-cell\"\n (click)=\"onItemSelect(item); $event.stopPropagation()\">\n <input \n type=\"checkbox\" \n [checked]=\"isSelected(item)\"\n (change)=\"onItemSelect(item)\">\n </div>\n\n <!-- Data cells -->\n <div\n *ngFor=\"let column of columns\"\n class=\"list-cell\"\n [style.width]=\"column.width\">\n {{ getCellValue(item, column) }}\n </div>\n </div>\n\n <!-- Empty State -->\n <div class=\"list-empty\" *ngIf=\"(data$ | async)?.length === 0\">\n <div class=\"empty-icon\">📋</div>\n <div class=\"empty-text\">{{ emptyMessage }}</div>\n </div>\n </div>\n </div>\n</div> ","import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Observable, of } from 'rxjs';\n\n/**\n * Table column configuration interface\n * @interface TableColumn\n * @since 2.1.0\n */\nexport interface TableColumn {\n key: string;\n label: string;\n sortable?: boolean;\n width?: string;\n type?: 'text' | 'number' | 'date' | 'boolean' | 'actions';\n align?: 'left' | 'center' | 'right';\n}\n\n/**\n * Table Component\n * \n * @description\n * A comprehensive table component for displaying and managing tabular data.\n * Originally designed for backoffice applications with data management needs.\n * Supports pagination, sorting, selection, and custom actions.\n * \n * @example\n * ```html\n * <!-- Basic table -->\n * <juvo-table\n * [data$]=\"tableData$\"\n * [columns]=\"[\n * { key: 'id', label: 'ID', width: '80px' },\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email', sortable: true },\n * { key: 'actions', label: 'Actions', type: 'actions' }\n * ]\"\n * [paginator]=\"true\"\n * [rows]=\"10\"\n * (rowSelected)=\"onRowSelected($event)\"\n * (actionTriggered)=\"onActionTriggered($event)\">\n * </juvo-table>\n * \n * <!-- Table with selection -->\n * <juvo-table\n * [data$]=\"userData$\"\n * [columns]=\"userColumns\"\n * [loading$]=\"userLoading$\"\n * [selectionMode]=\"'multiple'\"\n * [paginator]=\"true\"\n * [sortMode]=\"'single'\"\n * (selectionChange)=\"onSelectionChange($event)\">\n * </juvo-table>\n * ```\n * \n * @selector juvo-table\n * @since 2.1.0\n * @author Juvo Rafa Team\n */\n@Component({\n selector: 'juvo-table',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './juvo-table.component.html',\n styleUrl: './juvo-table.component.css'\n})\nexport class JuvoTableComponent {\n /** Observable data source */\n @Input() data$: Observable<any[]> = of([]);\n \n /** Column configuration */\n @Input() columns: TableColumn[] = [];\n \n /** Loading state observable */\n @Input() loading$: Observable<boolean> = of(false);\n \n /** Selection mode @default \"none\" */\n @Input() selectionMode: 'none' | 'single' | 'multiple' = 'none';\n \n /** Whether to show paginator @default false */\n @Input() paginator: boolean = false;\n \n /** Number of rows per page @default 10 */\n @Input() rows: number = 10;\n \n /** Sort mode @default \"single\" */\n @Input() sortMode: 'none' | 'single' | 'multiple' = 'single';\n \n /** Whether to show grid lines @default true */\n @Input() showGridlines: boolean = true;\n \n /** Whether to show striped rows @default true */\n @Input() striped: boolean = true;\n \n /** Whether to show hover effect @default true */\n @Input() hoverable: boolean = true;\n \n /** Table size variant @default \"medium\" */\n @Input() size: 'small' | 'medium' | 'large' = 'medium';\n \n /** Empty state message */\n @Input() emptyMessage: string = 'No records found';\n \n /** Loading message */\n @Input() loadingMessage: string = 'Loading data...';\n \n /** Emitted when a row is selected */\n @Output() rowSelected = new EventEmitter<any>();\n \n /** Emitted when selection changes */\n @Output() selectionChange = new EventEmitter<any[]>();\n \n /** Emitted when a row is clicked */\n @Output() rowClicked = new EventEmitter<any>();\n \n /** Emitted when column is sorted */\n @Output() sortChange = new EventEmitter<{ field: string, order: 'asc' | 'desc' }>();\n \n /** Emitted when page changes */\n @Output() pageChange = new EventEmitter<{ first: number, rows: number }>();\n \n /** Emitted when an action is triggered */\n @Output() actionTriggered = new EventEmitter<{ action: string, item: any }>();\n\n selectedRows: any[] = [];\n currentPage = 0;\n sortField?: string;\n sortOrder: 'asc' | 'desc' = 'asc';\n paginatedData: any[] = [];\n totalRecords = 0;\n\n // Expose Math for template\n Math = Math;\n\n /**\n * Gets CSS classes for the table container\n * @returns Combined CSS classes\n */\n get containerClasses(): string {\n let classes = `juvo-table juvo-table-${this.size}`;\n if (this.showGridlines) classes += ' gridlines';\n if (this.striped) classes += ' striped';\n if (this.hoverable) classes += ' hoverable';\n return classes;\n }\n\n /**\n * Handles row selection\n */\n onRowSelect(row: any, event?: Event): void {\n if (this.selectionMode === 'none') return;\n \n if (event) {\n event.stopPropagation();\n }\n \n const index = this.selectedRows.findIndex(selected => selected === row);\n \n if (this.selectionMode === 'single') {\n this.selectedRows = index > -1 ? [] : [row];\n } else if (this.selectionMode === 'multiple') {\n if (index > -1) {\n this.selectedRows.splice(index, 1);\n } else {\n this.selectedRows.push(row);\n }\n }\n \n this.rowSelected.emit(row);\n this.selectionChange.emit(this.selectedRows);\n }\n\n /**\n * Handles row click\n */\n onRowClick(row: any): void {\n this.rowClicked.emit(row);\n }\n\n /**\n * Handles column sort\n */\n onSort(column: TableColumn): void {\n if (!column.sortable || this.sortMode === 'none') return;\n \n if (this.sortField === column.key) {\n this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';\n } else {\n this.sortField = column.key;\n this.sortOrder = 'asc';\n }\n \n this.sortChange.emit({ field: column.key, order: this.sortOrder });\n }\n\n /**\n * Handles select all checkbox\n */\n selectAll(event: Event): void {\n const checkbox = event.target as HTMLInputElement;\n // This would need to be implemented based on current data\n // For now, just emit the selection change\n this.selectionChange.emit(this.selectedRows);\n }\n\n /**\n * Handles previous page navigation\n */\n previousPage(): void {\n if (this.currentPage > 0) {\n this.currentPage--;\n this.pageChange.emit({ first: this.currentPage * this.rows, rows: this.rows });\n }\n }\n\n /**\n * Handles next page navigation\n */\n nextPage(): void {\n this.currentPage++;\n this.pageChange.emit({ first: this.currentPage * this.rows, rows: this.rows });\n }\n\n /**\n * Checks if a row is selected\n */\n isSelected(row: any): boolean {\n return this.selectedRows.includes(row);\n }\n\n /**\n * Gets the display value for a cell\n */\n getCellValue(item: any, column: TableColumn): string {\n const value = item[column.key];\n \n switch (column.type) {\n case 'boolean':\n return value ? '✅' : '❌';\n case 'date':\n return value ? new Date(value).toLocaleDateString() : '';\n case 'number':\n return typeof value === 'number' ? value.toLocaleString() : '';\n case 'actions':\n return ''; // Actions are handled separately\n default:\n return value?.toString() || '';\n }\n }\n\n /**\n * Gets CSS classes for column header\n */\n getHeaderClasses(column: TableColumn): string {\n let classes = 'table-header-cell';\n if (column.sortable) classes += ' sortable';\n if (this.sortField === column.key) classes += ` sorted-${this.sortOrder}`;\n if (column.align) classes += ` text-${column.align}`;\n return classes;\n }\n\n /**\n * Gets CSS classes for table cell\n */\n getCellClasses(column: TableColumn): string {\n let classes = 'table-cell';\n if (column.align) classes += ` text-${column.align}`;\n return classes;\n }\n\n /**\n * Handles action button click\n */\n onAction(action: string, item: any): void {\n this.actionTriggered.emit({ action, item });\n }\n\n /**\n * Track by function for ngFor\n */\n trackByFn(index: number, item: any): any {\n return item.id || index;\n }\n} ","<div [class]=\"containerClasses\">\n <!-- Loading State -->\n <div class=\"table-loading\" *ngIf=\"loading$ | async\">\n <div class=\"loading-spinner\"></div>\n <div class=\"loading-text\">{{ loadingMessage }}</div>\n </div>\n\n <!-- Table Content -->\n <div class=\"table-wrapper\" *ngIf=\"!(loading$ | async)\">\n <table class=\"table\">\n <!-- Header -->\n <thead>\n <tr>\n <!-- Selection column -->\n <th *ngIf=\"selectionMode !== 'none'\" class=\"selection-column\">\n <input \n *ngIf=\"selectionMode === 'multiple'\"\n type=\"checkbox\" \n (change)=\"selectAll($event)\">\n </th>\n \n <!-- Data columns -->\n <th \n *ngFor=\"let column of columns\"\n [class]=\"getHeaderClasses(column)\"\n [style.width]=\"column.width\"\n (click)=\"onSort(column)\">\n {{ column.label }}\n <span class=\"sort-indicator\" *ngIf=\"column.sortable && sortField === column.key\">\n {{ sortOrder === 'asc' ? '↑' : '↓' }}\n </span>\n </th>\n </tr>\n </thead>\n\n <!-- Body -->\n <tbody>\n <!-- Data rows -->\n <tr \n *ngFor=\"let row of data$ | async; trackBy: trackByFn\"\n class=\"table-row\"\n [class.selected]=\"isSelected(row)\"\n (click)=\"onRowClick(row)\">\n \n <!-- Selection cell -->\n <td *ngIf=\"selectionMode !== 'none'\" class=\"selection-cell\">\n <input \n type=\"checkbox\" \n [checked]=\"isSelected(row)\"\n (change)=\"onRowSelect(row, $event)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n\n <!-- Data cells -->\n <td \n *ngFor=\"let column of columns\"\n [class]=\"getCellClasses(column)\"\n [style.width]=\"column.width\">\n \n <!-- Regular content -->\n <span *ngIf=\"column.type !== 'actions'\">\n {{ getCellValue(row, column) }}\n </span>\n \n <!-- Actions column -->\n <div *ngIf=\"column.type === 'actions'\" class=\"action-buttons\">\n <button \n type=\"button\" \n class=\"action-btn edit-btn\"\n (click)=\"onAction('edit', row); $event.stopPropagation()\"\n title=\"Edit\">\n ✏️\n </button>\n <button \n type=\"button\" \n class=\"action-btn delete-btn\"\n (click)=\"onAction('delete', row); $event.stopPropagation()\"\n title=\"Delete\">\n 🗑️\n </button>\n </div>\n </td>\n </tr>\n\n <!-- Empty state -->\n <tr *ngIf=\"(data$ | async)?.length === 0\" class=\"empty-row\">\n <td [attr.colspan]=\"columns.length + (selectionMode !== 'none' ? 1 : 0)\">\n <div class=\"empty-state\">\n <div class=\"empty-icon\">📄</div>\n <div class=\"empty-text\">{{ emptyMessage }}</div>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n <div class=\"table-pagination\" *ngIf=\"paginator && ((data$ | async)?.length || 0) > 0\">\n <div class=\"pagination-info\">\n Showing {{ currentPage * rows + 1 }} to {{ Math.min((currentPage + 1) * rows, totalRecords) }} of {{ totalRecords }} entries\n </div>\n <div class=\"pagination-controls\">\n <button \n type=\"button\" \n class=\"page-btn\"\n [disabled]=\"currentPage === 0\"\n (click)=\"previousPage()\">\n Previous\n </button>\n <span class=\"page-info\">Page {{ currentPage + 1 }}</span>\n <button \n type=\"button\" \n class=\"page-btn\"\n [disabled]=\"(currentPage + 1) * rows >= totalRecords\"\n (click)=\"nextPage()\">\n Next\n </button>\n </div>\n </div>\n</div> ","import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/**\n * Tab menu item interface\n * @interface TabMenuItem\n * @since 2.1.0\n */\nexport interface TabMenuItem {\n id: string;\n label: string;\n icon?: string;\n disabled?: boolean;\n badge?: string | number;\n routerLink?: string;\n}\n\n/**\n * Tab Menu Component\n * \n * @description\n * A horizontal tab navigation component for organizing content into tabs.\n * Originally designed for backoffice applications with multiple content sections.\n * Supports icons, badges, disabled states, and router integration.\n * \n * @example\n * ```html\n * <!-- Basic tab menu -->\n * <juvo-tab-menu\n * [items]=\"tabItems\"\n * [activeItemId]=\"activeTab\"\n * (tabChange)=\"onTabChange($event)\">\n * </juvo-tab-menu>\n * \n * <!-- Tab menu with icons and badges -->\n * <juvo-tab-menu\n * [items]=\"[\n * { id: 'overview', label: 'Overview', icon: '📊' },\n * { id: 'users', label: 'Users', icon: '👥', badge: '12' },\n * { id: 'settings', label: 'Settings', icon: '⚙️', disabled: true }\n * ]\"\n * [activeItemId]=\"currentTab\"\n * (tabChange)=\"handleTabChange($event)\">\n * </juvo-tab-menu>\n * ```\n * \n * @selector juvo-tab-menu\n * @since 2.1.0\n * @author Juvo Rafa Team\n */\n@Component({\n selector: 'juvo-tab-menu',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './juvo-tab-menu.component.html',\n styleUrl: './juvo-tab-menu.component.css'\n})\nexport class JuvoTabMenuComponent {\n /** Array of tab menu items */\n @Input() items: TabMenuItem[] = [];\n \n /** ID of the currently active tab */\n @Input() activeItemId?: string;\n \n /** Tab menu style variant @default \"line\" */\n @Input() variant: 'line' | 'pills' | 'cards' = 'line';\n \n /** Tab menu size @default \"medium\" */\n @Input() size: 'small' | 'medium' | 'large' = 'medium';\n \n /** Whether tabs are scrollable on overflow @default false */\n @Input() scrollable: boolean = false;\n \n /** Emitted when a tab is clicked */\n @Output() tabChange = new EventEmitter<TabMenuItem>();\n \n /** Emitted when active tab changes */\n @Output() activeItemChange = new EventEmitter<TabMenuItem>();\n\n /**\n * Gets CSS classes for the tab menu container\n * @returns Combined CSS classes\n */\n get containerClasses(): string {\n let classes = `tab-menu tab-menu-${this.variant} tab-menu-${this.size}`;\n if (this.scrollable) classes += ' scrollable';\n return classes;\n }\n\n /**\n * Gets CSS classes for a specific tab item\n * @param item Tab menu item\n * @returns Combined CSS classes\n */\n getTabClasses(item: TabMenuItem): string {\n let classes = 'tab-item';\n if (item.id === this.activeItemId) classes += ' active';\n if (item.disabled) classes += ' disabled';\n return classes;\n }\n\n /**\n * Handles tab click\n * @param item Clicked tab item\n */\n onTabClick(item: TabMenuItem): void {\n if (item.disabled) return;\n \n this.activeItemId = item.id;\n this.tabChange.emit(item);\n this.activeItemChange.emit(item);\n }\n\n /**\n * Checks if a tab is active\n * @param item Tab menu item\n * @returns Whether the tab is active\n */\n isActive(item: TabMenuItem): boolean {\n return item.id === this.activeItemId;\n }\n} ","<div [class]=\"containerClasses\">\n <div class=\"tab-list\" role=\"tablist\">\n <button\n *ngFor=\"let item of items\"\n [class]=\"getTabClasses(item)\"\n [disabled]=\"item.disabled\"\n [attr.aria-selected]=\"isActive(item)\"\n [attr.aria-controls]=\"item.id + '-panel'\"\n role=\"tab\"\n type=\"button\"\n (click)=\"onTabClick(item)\">\n \n <!-- Icon -->\n <span class=\"tab-icon\" *ngIf=\"item.icon\">\n {{ item.icon }}\n </span>\n \n <!-- Label -->\n <span class=\"tab-label\">\n {{ item.label }}\n </span>\n \n <!-- Badge -->\n <span class=\"tab-badge\" *ngIf=\"item.badge\">\n {{ item.badge }}\n </span>\n </button>\n </div>\n</div> ","import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/**\n * Notification types\n * @type NotificationType\n * @since 2.1.0\n */\nexport type NotificationType = 'success' | 'error' | 'warning' | 'info';\n\n/**\n * Notification position options\n * @type NotificationPosition\n * @since 2.1.0\n */\nexport type NotificationPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';\n\n/**\n * Notification interface\n * @interface Notification\n * @since 2.1.0\n */\nexport interface Notification {\n id?: string;\n type: NotificationType;\n title: string;\n message?: string;\n duration?: number;\n persistent?: boolean;\n}\n\n/**\n * Notification Component\n * \n * @description\n * A flexible notification system for displaying messages to users.\n * Supports different types (success, error, warning, info) and positions.\n * Originally designed for backoffice applications to provide user feedback.\n * \n * @example\n * ```html\n * <!-- Basic notification -->\n * <juvo-notification\n * type=\"success\"\n * title=\"Operation successful\"\n * message=\"The item was saved successfully.\"\n * [duration]=\"5000\"\n * (onClose)=\"onNotificationClose()\">\n * </juvo-notification>\n * \n * <!-- Multiple notifications container -->\n * <juvo-notification\n * *ngFor=\"let notification of notifications\"\n * [type]=\"notification.type\"\n * [title]=\"notification.title\"\n * [message]=\"notification.message\"\n * [duration]=\"notification.duration\"\n * position=\"top-right\"\n * (onClose)=\"removeNotification(notification.id)\">\n * </juvo-notification>\n * ```\n * \n * @selector juvo-notification\n * @since 2.1.0\n * @author Juvo Rafa Team\n */\n@Component({\n selector: 'juvo-notification',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './juvo-notification.component.html',\n styleUrl: './juvo-notification.component.css'\n})\nexport class JuvoNotificationComponent {\n /** Notification type @default \"info\" */\n @Input() type: NotificationType = 'info';\n \n /** Notification title */\n @Input() title: string = '';\n \n /** Notification message content */\n @Input() message?: string;\n \n /** Auto-dismiss duration in milliseconds. 0 means no auto-dismiss @default 5000 */\n @Input() duration: number = 5000;\n \n /** Whether the notification persists until manually closed @default false */\n @Input() persistent: boolean = false;\n \n /** Position of the notification @default \"top-right\" */\n @Input() position: NotificationPosition = 'top-right';\n \n /** Whether to show close button @default true */\n @Input() closable: boolean = true;\n \n /** Whether the notification is visible @default true */\n