kaspacom-ui
Version:
UI Component Library for KaspaCom DeFi Applications
1 lines • 368 kB
Source Map (JSON)
{"version":3,"file":"kaspacom-ui.mjs","sources":["../../../projects/kaspacom-ui/src/lib/icons/icon/icon.component.ts","../../../projects/kaspacom-ui/src/lib/icons/icon/icon.component.html","../../../projects/kaspacom-ui/src/lib/modals/kc-base-modal/kc-base-modal.component.ts","../../../projects/kaspacom-ui/src/lib/modals/kc-base-modal/kc-base-modal.component.html","../../../projects/kaspacom-ui/src/lib/loading/spinner/spinner.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/button/button.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/checkbox/checkbox.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-select/dropdown-options/dropdown-options.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-select/dropdown-options/dropdown-options.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/services/responsive.service.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-select/dropdown-select.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-select/dropdown-select.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-multiselect/dropdown-multiselect-options/dropdown-multiselect-options.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-multiselect/dropdown-multiselect-options/dropdown-multiselect-options.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-multiselect/dropdown-multiselect.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/dropdown-multiselect/dropdown-multiselect.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/components/chip/chip.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/split-button/split-button-options/split-button-options.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/split-button/split-button.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/switch-navigation/switch-navigation.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/components/switch-navigation/switch-navigation.component.html","../../../projects/kaspacom-ui/src/lib/form-controls/components/toggle/toggle.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/kc-input/kc-input.component.ts","../../../projects/kaspacom-ui/src/lib/form-controls/kc-input/kc-input.component.html","../../../projects/kaspacom-ui/src/lib/card/kc-card.component.ts","../../../projects/kaspacom-ui/src/lib/card/kc-card.component.html","../../../projects/kaspacom-ui/src/lib/loading/skeleton/skeleton-block.component.ts","../../../projects/kaspacom-ui/src/lib/loading/skeleton/skeleton-block.component.html","../../../projects/kaspacom-ui/src/lib/data/table/table.component.ts","../../../projects/kaspacom-ui/src/lib/data/table/table.component.html","../../../projects/kaspacom-ui/src/lib/data/table/directive/left-sticky.directive.ts","../../../projects/kaspacom-ui/src/lib/data/table/directive/right-sticky.directive.ts","../../../projects/kaspacom-ui/src/lib/data/table/component/sortable.component.ts","../../../projects/kaspacom-ui/src/lib/data/table/component/sortable.component.html","../../../projects/kaspacom-ui/src/lib/snackbar/snackbar.models.ts","../../../projects/kaspacom-ui/src/lib/snackbar/notification.service.ts","../../../projects/kaspacom-ui/src/lib/snackbar/kc-snackbar/kc-snackbar.component.ts","../../../projects/kaspacom-ui/src/lib/snackbar/kc-snackbar/kc-snackbar.component.html","../../../projects/kaspacom-ui/src/lib/directives/tooltip/tooltip.directive.ts","../../../projects/kaspacom-ui/src/lib/showcase/component/table/table-showcase.component.ts","../../../projects/kaspacom-ui/src/lib/showcase/component/table/table-showcase.component.html","../../../projects/kaspacom-ui/src/lib/showcase/design-system-showcase.component.ts","../../../projects/kaspacom-ui/src/lib/showcase/design-system-showcase.component.html","../../../projects/kaspacom-ui/src/public-api.ts","../../../projects/kaspacom-ui/src/kaspacom-ui.ts"],"sourcesContent":["import {\n Component,\n EventEmitter,\n HostListener,\n Inject,\n Input,\n OnInit,\n Output,\n} from '@angular/core';\nimport { DOCUMENT, NgClass, NgStyle } from '@angular/common';\nimport { ComponentSize } from '../../form-controls/types/sizing.type';\n\n@Component({\n // tslint:disable-next-line:component-selector\n selector: 'kc-icon',\n templateUrl: './icon.component.html',\n styleUrls: ['./icon.component.scss'],\n standalone: true,\n imports: [NgStyle, NgClass],\n})\nexport class KcIconComponent {\n @Input() iconClass!: string;\n @Input() public readonly disabled: boolean = false;\n @Input() size: ComponentSize = 'sm';\n @Input() public iconSize: ComponentSize = 'sm';\n @Input() public readonly classes?: string;\n @Input() public readonly isDefaultColor: boolean = true;\n @Input() public color?: string;\n\n constructor(@Inject(DOCUMENT) private _document: Document) {}\n}\n","<div\n [class]=\"'icon-wrapper ' + classes\"\n [ngStyle]=\"{color: color ? color : ''}\"\n [ngClass]=\"{\n disabled: disabled,\n xs: size === 'xs',\n sm: size === 'sm',\n md: size === 'md',\n lg: size === 'lg',\n xlg: size === 'xlg',\n 'default-color': isDefaultColor\n }\"\n>\n <div class=\"{{ iconClass }}\" [ngClass]=\"{icon: color ? false : true}\"></div>\n</div>\n","import { Component, ElementRef, input, output } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ComponentSize } from '../../form-controls/types/sizing.type';\nimport { KcIconComponent } from '../../icons/icon/icon.component';\nimport { trigger, transition, style, animate } from '@angular/animations';\n\n@Component({\n selector: 'kc-base-modal',\n standalone: true,\n imports: [CommonModule, KcIconComponent],\n templateUrl: './kc-base-modal.component.html',\n styleUrls: ['./kc-base-modal.component.scss'],\n animations: [\n trigger('modalSlide', [\n transition(':enter', [\n style({ transform: 'translateY(100%)', opacity: 0 }),\n animate('350ms ease-out', style({ transform: 'translateY(0)', opacity: 1 }))\n ]),\n transition(':leave', [\n animate('300ms ease-in', style({ transform: 'translateY(100%)', opacity: 0 }))\n ])\n ]),\n trigger('overlayFade', [\n transition(':enter', [\n style({ opacity: 0 }),\n animate('250ms ease-out', style({ opacity: 1 }))\n ]),\n transition(':leave', [\n animate('200ms ease-in', style({ opacity: 0 }))\n ])\n ])\n ]\n})\nexport class KcBaseModalComponent {\n // Inputs\n showCloseButton = input<boolean>(true);\n title = input<string | undefined>(undefined);\n titleIconClass = input<string | undefined>(undefined);\n showHeaderSeperator = input<boolean>(true);\n autoWidth = input<boolean>(false);\n\n // Component size constants for the template\n componentSizes: Record<string, ComponentSize> = {\n XS: 'xs',\n SM: 'sm',\n MD: 'md',\n LG: 'lg',\n XLG: 'xlg'\n };\n \n // Outputs\n close = output<void>();\n\n constructor(private el: ElementRef) {}\n\n onClose(): void {\n this.el.nativeElement.classList.add('closing');\n\n setTimeout(() => {\n this.close.emit();\n }, 300);\n }\n}\n","<div class=\"modal-overlay\">\n <div class=\"modal-container\" [class.auto-width]=\"autoWidth()\">\n <div class=\"modal-header\" [class.header-separator]=\"showHeaderSeperator()\">\n <div class=\"modal-title\">\n <span *ngIf=\"titleIconClass()\" class=\"modal-title-icon\">\n <kc-icon [iconClass]=\"titleIconClass()!\" [size]=\"componentSizes['MD']\"></kc-icon>\n </span>\n <span *ngIf=\"title()\" class=\"modal-title-text typo-title-3\">{{ title() }}</span>\n </div>\n <div class=\"modal-header-controls\">\n <ng-content select=\"[rightSideSlot]\"></ng-content>\n <button *ngIf=\"showCloseButton()\" class=\"close-button\" (click)=\"onClose()\">\n <kc-icon iconClass=\"icon-close\"></kc-icon>\n </button>\n </div>\n </div>\n <div class=\"modal-content typo-text-3\">\n <ng-content></ng-content>\n </div>\n </div>\n</div> ","import { Component, input } from '@angular/core';\nimport { NgClass } from '@angular/common';\nimport { ComponentSize } from '../../form-controls/types/sizing.type';\n\n@Component({\n selector: 'kc-spinner',\n standalone: true,\n imports: [NgClass],\n template: `\n <div class=\"spinner\" [ngClass]=\"size()\"></div>\n `,\n styleUrls: ['./spinner.component.scss']\n})\nexport class KcSpinnerComponent {\n size = input<ComponentSize>('md');\n} ","import { Component, input, output, EventEmitter, HostBinding, ContentChild, TemplateRef } from '@angular/core';\r\nimport { NgClass, NgTemplateOutlet } from '@angular/common';\r\nimport { ComponentSize } from '../../types/sizing.type';\r\nimport { KcSpinnerComponent } from '../../../loading/spinner/spinner.component';\r\nimport { KcIconComponent } from '../../../icons/icon/icon.component';\r\nimport { ButtonVariant } from '../../types/button-variant.type';\r\n\r\n@Component({\r\n selector: 'kc-button',\r\n standalone: true,\r\n imports: [NgClass, NgTemplateOutlet, KcSpinnerComponent, KcIconComponent],\r\n template: `\r\n <button\r\n class=\"app-button\"\r\n [ngClass]=\"[\r\n variant(),\r\n size(),\r\n isFullWidth() ? 'full-width' : '',\r\n isDisabled() || isLoading() ? 'disabled' : '',\r\n isLoading() ? 'loading' : '',\r\n role() ? 'role-' + role() : '',\r\n getTypographyClass()\r\n ]\"\r\n [disabled]=\"isDisabled() || isLoading()\"\r\n (click)=\"handleClick($event)\"\r\n >\r\n <div class=\"button-content\">\r\n @if (isLoading()) {\r\n <div class=\"loading-content\">\r\n <kc-spinner [size]=\"getSpinnerSize()\"></kc-spinner>\r\n @if (loadingText()) {\r\n <span>{{ loadingText() }}</span>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"button-inner-content\">\r\n @if (prefixIcon()) {\r\n <kc-icon \r\n [iconClass]=\"prefixIcon()!\" \r\n [color]=\"prefixIconColor()\"\r\n [size]=\"getPrefixIconSize()\">\r\n </kc-icon>\r\n }\r\n @if (prefixTemplateRef) {\r\n <ng-container [ngTemplateOutlet]=\"prefixTemplateRef\"></ng-container>\r\n }\r\n @if (text()) {\r\n <span>{{ text() }}</span>\r\n }\r\n @if (suffixIcon()) {\r\n <kc-icon \r\n [iconClass]=\"suffixIcon()!\" \r\n [color]=\"suffixIconColor()\"\r\n [size]=\"getSuffixIconSize()\">\r\n </kc-icon>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </button>\r\n `,\r\n styleUrls: ['./button.component.scss'],\r\n host: {\r\n '[style.display]': \"isFullWidth() ? 'block' : 'inline-block'\",\r\n '[style.width]': \"isFullWidth() ? '100%' : 'auto'\"\r\n }\r\n})\r\nexport class KcButtonComponent {\r\n text = input<string>('');\r\n variant = input<ButtonVariant>('primary');\r\n size = input<ComponentSize>('md');\r\n isLoading = input<boolean>(false);\r\n isFullWidth = input<boolean>(false);\r\n isDisabled = input<boolean>(false);\r\n role = input<'success' | 'info' | 'warning' | 'danger' | 'neutral' | null>(null);\r\n prefixIcon = input<string | undefined>(undefined);\r\n suffixIcon = input<string | undefined>(undefined);\r\n prefixIconColor = input<string | undefined>(undefined);\r\n suffixIconColor = input<string | undefined>(undefined);\r\n prefixIconSize = input<ComponentSize | undefined>(undefined);\r\n suffixIconSize = input<ComponentSize | undefined>(undefined);\r\n loadingText = input<string | undefined>(undefined);\r\n \r\n @ContentChild('prefixTemplate', { static: false }) prefixTemplateRef?: TemplateRef<any>;\r\n \r\n buttonClick = output<MouseEvent>();\r\n \r\n handleClick(event: MouseEvent): void {\r\n if (!this.isDisabled() && !this.isLoading()) {\r\n this.buttonClick.emit(event);\r\n }\r\n }\r\n \r\n getSpinnerSize(): ComponentSize {\r\n // Map button size to appropriate spinner size\r\n const sizeMap: Record<ComponentSize, ComponentSize> = {\r\n 'xs': 'xs',\r\n 'sm': 'xs',\r\n 'md': 'sm',\r\n 'lg': 'md',\r\n 'xlg': 'lg'\r\n };\r\n \r\n return sizeMap[this.size()];\r\n }\r\n \r\n getTypographyClass(): string {\r\n // Map button size to appropriate typography class\r\n const typographyMap: Record<ComponentSize, string> = {\r\n 'xs': 'typo-button-small',\r\n 'sm': 'typo-button-small',\r\n 'md': 'typo-button-medium',\r\n 'lg': 'typo-button-large',\r\n 'xlg': 'typo-button-large'\r\n };\r\n \r\n return typographyMap[this.size()];\r\n }\r\n \r\n getPrefixIconSize(): ComponentSize {\r\n // Use custom size if provided, otherwise default to 'sm'\r\n return this.prefixIconSize() || 'sm';\r\n }\r\n \r\n getSuffixIconSize(): ComponentSize {\r\n // Use custom size if provided, otherwise default to 'sm'\r\n return this.suffixIconSize() || 'sm';\r\n }\r\n} ","import { Component, forwardRef, input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';\nimport { ComponentSize } from '../../types/sizing.type';\n\n@Component({\n selector: 'kc-checkbox',\n standalone: true,\n imports: [CommonModule, FormsModule],\n template: `\n <div \n class=\"checkbox-container\" \n [class.checked]=\"checked\"\n [class.disabled]=\"isDisabled()\"\n [ngClass]=\"sizeClass()\"\n (click)=\"toggle()\"\n >\n <div class=\"checkbox\">\n <div class=\"checkbox-inner\" [class.checked]=\"checked\">\n <svg *ngIf=\"checked\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n </div>\n </div>\n <label *ngIf=\"label()\" class=\"checkbox-label\">{{ label() }}</label>\n </div>\n `,\n styles: [`\n .checkbox-container {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n user-select: none;\n }\n \n .checkbox-container.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n \n .checkbox {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n .checkbox-inner {\n border-radius: 4px;\n border: 2px solid var(--gray-50);\n background-color: var(--gray-20);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n box-sizing: border-box;\n position: relative;\n }\n \n .checkbox-inner.checked {\n border-color: var(--kaspa-50);\n background-color: var(--kaspa-50);\n }\n \n .checkbox-inner svg {\n color: white;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 100%;\n height: 100%;\n }\n \n /* Sizing */\n .checkbox-container.xs .checkbox-inner {\n width: 14px;\n height: 14px;\n min-width: 14px;\n min-height: 14px;\n }\n \n .checkbox-container.sm .checkbox-inner {\n width: 16px;\n height: 16px;\n min-width: 16px;\n min-height: 16px;\n }\n \n .checkbox-container.md .checkbox-inner {\n width: 18px;\n height: 18px;\n min-width: 18px;\n min-height: 18px;\n }\n \n .checkbox-container.lg .checkbox-inner {\n width: 20px;\n height: 20px;\n min-width: 20px;\n min-height: 20px;\n }\n \n .checkbox-container.xlg .checkbox-inner {\n width: 24px;\n height: 24px;\n min-width: 24px;\n min-height: 24px;\n }\n \n .checkbox-container.xs svg {\n width: 10px !important;\n height: 10px !important;\n }\n \n .checkbox-container.sm svg {\n width: 12px !important;\n height: 12px !important;\n }\n \n .checkbox-container.md svg {\n width: 14px !important;\n height: 14px !important;\n }\n \n .checkbox-container.lg svg {\n width: 16px !important;\n height: 16px !important;\n }\n \n .checkbox-container.xlg svg {\n width: 18px !important;\n height: 18px !important;\n }\n \n /* Typography for labels */\n .checkbox-container.xs .checkbox-label {\n font-size: 0.75rem;\n }\n \n .checkbox-container.sm .checkbox-label {\n font-size: 0.875rem;\n }\n \n .checkbox-container.md .checkbox-label {\n font-size: 1rem;\n }\n \n .checkbox-container.lg .checkbox-label {\n font-size: 1.125rem;\n }\n \n .checkbox-container.xlg .checkbox-label {\n font-size: 1.25rem;\n }\n `],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => KcCheckboxComponent),\n multi: true\n }\n ]\n})\nexport class KcCheckboxComponent implements ControlValueAccessor, OnInit, OnChanges {\n // Inputs\n size = input<ComponentSize>('md');\n isDisabled = input<boolean>(false);\n label = input<string>('');\n isChecked = input<boolean>(false);\n \n // State\n checked = false;\n \n // Output\n @Output() checkedChange = new EventEmitter<boolean>();\n \n // ControlValueAccessor\n private onChange: (value: boolean) => void = () => {};\n private onTouched: () => void = () => {};\n \n ngOnInit() {\n // Initialize the checked state from the isChecked input\n this.checked = this.isChecked();\n }\n \n ngOnChanges(changes: SimpleChanges) {\n // Update checked state when isChecked input changes\n if (changes['isChecked']) {\n this.checked = this.isChecked();\n }\n }\n \n // Calculate size class\n sizeClass() {\n // With string-based types, we can just return the size directly\n return this.size();\n }\n \n // Toggle the checkbox\n toggle() {\n if (this.isDisabled()) {\n return;\n }\n \n this.onTouched();\n this.checked = !this.checked;\n \n // Update the input value\n // Emit event for ngModel binding\n this.checkedChange.emit(this.checked);\n \n // Emit event for reactive forms\n this.onChange(this.checked);\n }\n \n // ControlValueAccessor implementation\n writeValue(value: boolean): void {\n this.checked = !!value;\n }\n \n registerOnChange(fn: (value: boolean) => 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 // Update the disabled state\n }\n} ","import { Component, EventEmitter, TemplateRef, Output, OnInit, OnDestroy, Input, ContentChild } from '@angular/core';\nimport { CommonModule, NgClass } from '@angular/common';\nimport { DropdownOption } from '../dropdown-select.models';\nimport { DropdownVariant } from '../../../types/dropdown-variant.type';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\nimport { Subject, debounceTime, takeUntil } from 'rxjs';\n\n@Component({\n selector: 'kc-dropdown-options',\n standalone: true,\n imports: [CommonModule, NgClass, ReactiveFormsModule],\n templateUrl: './dropdown-options.component.html',\n styleUrls: ['./dropdown-options.component.scss']\n})\nexport class KcDropdownOptionsComponent implements OnInit, OnDestroy {\n options: DropdownOption[] = [];\n filteredOptions: DropdownOption[] = [];\n selectedValue: any = null;\n variant: DropdownVariant = 'secondary';\n \n @Input() optionsEllipsis: boolean = false;\n @Input() isFullscreenSelection: boolean = false;\n @Input() isSearchable: boolean = false;\n @Input() searchField: string = 'label';\n @Input() customTemplate: TemplateRef<any> | null = null;\n @ContentChild('emptyStateTemplate') emptyStateTemplate: TemplateRef<any> | null = null;\n \n searchControl = new FormControl('');\n private destroy$ = new Subject<void>();\n \n @Output() optionSelected = new EventEmitter<DropdownOption>();\n @Output() closeRequested = new EventEmitter<void>();\n\n ngOnInit(): void {\n this.filteredOptions = [...this.options];\n \n if (this.isSearchable) {\n this.searchControl.valueChanges.pipe(\n debounceTime(128),\n takeUntil(this.destroy$)\n ).subscribe(value => {\n this.filterOptions(value || '');\n });\n }\n }\n \n filterOptions(searchText: string): void {\n if (!searchText.trim()) {\n this.filteredOptions = [...this.options];\n return;\n }\n \n const searchLower = searchText.toLowerCase();\n this.filteredOptions = this.options.filter(option => {\n const field = this.searchField === 'label' ? option.label : \n (option as any)[this.searchField] || '';\n return String(field).toLowerCase().includes(searchLower);\n });\n }\n\n isSelected(option: DropdownOption): boolean {\n return this.selectedValue === option.value;\n }\n\n selectOption(option: DropdownOption): void {\n this.optionSelected.emit(option);\n }\n \n closeFullscreen(): void {\n this.closeRequested.emit();\n }\n \n getTypographyClass(): string {\n // Using the same typography class as the dropdown options\n return 'typo-text-3';\n }\n \n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n}\n","<div class=\"dropdown-options-container\" [ngClass]=\"[variant, isFullscreenSelection ? 'fullscreen-mode' : '']\">\n <!-- Fullscreen header with close button -->\n <div *ngIf=\"isFullscreenSelection\" class=\"fullscreen-header\">\n <div class=\"fullscreen-title typo-title-3\">Select an option</div>\n <button class=\"close-button\" (click)=\"closeFullscreen()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n \n <!-- Search input -->\n <div *ngIf=\"isSearchable\" class=\"search-container\">\n <div class=\"search-input-wrapper\">\n <svg class=\"search-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n <input \n type=\"text\" \n class=\"search-input\"\n [formControl]=\"searchControl\"\n placeholder=\"Search...\" \n [ngClass]=\"getTypographyClass()\"\n autocomplete=\"off\"\n />\n </div>\n </div>\n \n <div class=\"options-list\">\n <div\n *ngFor=\"let option of filteredOptions\"\n class=\"dropdown-option\"\n [class.disabled]=\"option.disabled\"\n [class.selected]=\"isSelected(option)\"\n [class.ellipsis]=\"optionsEllipsis\"\n [class.variant-primary]=\"variant === 'primary'\"\n [class.variant-secondary]=\"variant === 'secondary'\"\n [class.variant-tertiary]=\"variant === 'tertiary'\"\n [ngClass]=\"getTypographyClass()\"\n (click)=\"option.disabled ? null : selectOption(option)\">\n \n <!-- Use custom template when provided -->\n <ng-container *ngIf=\"customTemplate; else defaultTemplate\">\n <ng-container *ngTemplateOutlet=\"customTemplate; context: { $implicit: option }\"></ng-container>\n </ng-container>\n \n <!-- Default template -->\n <ng-template #defaultTemplate>\n {{ option.label }}\n </ng-template>\n </div>\n \n <!-- No results message -->\n <div *ngIf=\"filteredOptions.length === 0\" class=\"no-results\" [ngClass]=\"getTypographyClass()\">\n <!-- Use projected empty state template when provided -->\n <ng-content select=\"[emptyStateTemplate]\" *ngIf=\"emptyStateTemplate\"></ng-content>\n \n <!-- Default empty state template when no projection provided -->\n <ng-container *ngIf=\"!emptyStateTemplate\">\n <div class=\"typo-caption\">No matching options found</div>\n </ng-container>\n </div>\n </div>\n</div> ","import { Injectable, OnDestroy, inject, signal } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { Subject, takeUntil } from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ResponsiveService implements OnDestroy {\n private readonly destroy$ = new Subject<void>();\n private readonly breakpointObserver = inject(BreakpointObserver);\n \n // Signal that indicates if the screen is in mobile size (XS and below)\n isMobile = signal<boolean>(false);\n \n // Signal for screens <= 480px\n isExtraSmallScreen = signal<boolean>(false);\n \n constructor() {\n // Set the initial values based on the current screen size\n this.isMobile.set(this.breakpointObserver.isMatched(Breakpoints.XSmall));\n this.isExtraSmallScreen.set(this.breakpointObserver.isMatched('(max-width: 480px)'));\n \n // Observe changes to the breakpoints\n this.breakpointObserver\n .observe([Breakpoints.XSmall, '(max-width: 480px)'])\n .pipe(takeUntil(this.destroy$))\n .subscribe(result => {\n // Update the signals when the breakpoint states change\n this.isMobile.set(result.breakpoints[Breakpoints.XSmall]);\n this.isExtraSmallScreen.set(result.breakpoints['(max-width: 480px)']);\n });\n }\n \n // Also provide a method for custom breakpoint checking\n isBreakpointMatched(breakpoint: string): boolean {\n return this.breakpointObserver.isMatched(breakpoint);\n }\n \n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n} ","import {\r\n Component,\r\n ContentChild,\r\n effect,\r\n ElementRef,\r\n inject,\r\n input,\r\n OnDestroy,\r\n OnInit,\r\n output,\r\n TemplateRef,\r\n ViewChild\r\n} from '@angular/core';\r\nimport {CommonModule, NgClass} from '@angular/common';\r\nimport {Overlay, OverlayModule, OverlayRef} from '@angular/cdk/overlay';\r\nimport {ComponentPortal} from '@angular/cdk/portal';\r\nimport {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule} from '@angular/forms';\r\nimport {Subject, takeUntil} from 'rxjs';\r\n\r\nimport {KcDropdownOptionsComponent} from './dropdown-options/dropdown-options.component';\r\nimport {DropdownOption} from './dropdown-select.models';\r\nimport {DropdownVariant} from '../../types/dropdown-variant.type';\r\nimport {ComponentSize} from '../../types/sizing.type';\r\nimport {KcIconComponent} from '../../../icons/icon/icon.component';\r\nimport {ResponsiveService} from '../../services/responsive.service';\r\n\r\n@Component({\r\n selector: 'kc-dropdown-select',\r\n standalone: true,\r\n imports: [CommonModule, NgClass, FormsModule, ReactiveFormsModule, KcIconComponent, OverlayModule],\r\n templateUrl: './dropdown-select.component.html',\r\n styleUrls: ['./dropdown-select.component.scss'],\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n useExisting: KcDropdownSelectComponent,\r\n multi: true\r\n }\r\n ]\r\n})\r\nexport class KcDropdownSelectComponent implements ControlValueAccessor, OnInit, OnDestroy {\r\n // Constants\r\n private readonly destroy$ = new Subject<void>();\r\n private readonly overlay = inject(Overlay);\r\n private readonly responsiveService = inject(ResponsiveService);\r\n\r\n // Signals\r\n options = input<DropdownOption[]>([]);\r\n placeholder = input<string>('Select an option');\r\n size = input<ComponentSize>('md');\r\n variant = input<DropdownVariant>('secondary');\r\n isFullWidth = input<boolean>(false);\r\n isDisabled = input<boolean>(false);\r\n optionsEllipsis = input<boolean>(false);\r\n isFullscreenSelection = input<boolean>(false);\r\n icon = input<string>('');\r\n iconSize = input<ComponentSize | undefined>(undefined);\r\n useContentWidth = input<boolean>(true);\r\n maxWidth = input<string | null>(null);\r\n showToggleIcon = input<boolean>(true);\r\n\r\n // Value input for direct binding\r\n value = input<any>(null);\r\n\r\n // Search-related inputs\r\n isSearchable = input<boolean>(false);\r\n searchField = input<string>('label');\r\n\r\n // Custom item template\r\n @ContentChild('optionTemplate') optionTemplate: TemplateRef<any> | null = null;\r\n @ContentChild('prefixTemplate') prefixTemplateRef: TemplateRef<any> | null = null;\r\n @ContentChild('emptyStateTemplate') emptyStateTemplateRef: TemplateRef<any> | null = null;\r\n\r\n // Output signals\r\n valueChange = output<any>();\r\n\r\n // Other properties\r\n @ViewChild('dropdownTrigger') dropdownTrigger!: ElementRef;\r\n isOpen = false;\r\n private overlayRef: OverlayRef | null = null;\r\n private _value: any = null;\r\n private onChange: (value: any) => void = () => {};\r\n private onTouched: () => void = () => {};\r\n\r\n // We modify the isDisabled check to consider both the input signal and our internal state\r\n private _internalDisabled = false;\r\n\r\n constructor() {\r\n // Use an effect to respond to changes in the input value\r\n effect(() => {\r\n const newValue = this.value();\r\n if (this._value !== newValue) {\r\n this._value = newValue;\r\n }\r\n });\r\n }\r\n \r\n ngOnInit(): void {\r\n // We can't modify the input signal directly, so we'll create a computed property\r\n // that will check both the provided isFullscreenSelection and the screen size\r\n }\r\n\r\n // Helper method to check disabled state combining both sources\r\n isComponentDisabled(): boolean {\r\n return this.isDisabled() || this._internalDisabled;\r\n }\r\n\r\n // Get the display value for the dropdown\r\n getDisplayValue(): string {\r\n if (this._value === null || this._value === undefined) {\r\n return this.placeholder();\r\n }\r\n\r\n const selectedOption = this.options().find(option => option.value === this._value);\r\n return selectedOption ? selectedOption.label : this.placeholder();\r\n }\r\n\r\n // Map dropdown size to typography class\r\n getTypographyClass(): string {\r\n const typographyMap: Record<ComponentSize, string> = {\r\n 'xs': 'typo-text-1',\r\n 'sm': 'typo-text-2',\r\n 'md': 'typo-text-3',\r\n 'lg': 'typo-text-4',\r\n 'xlg': 'typo-text-5'\r\n };\r\n\r\n return typographyMap[this.size()];\r\n }\r\n\r\n // Get icon size - use custom size if provided, otherwise default to 'sm'\r\n getIconSize(): ComponentSize {\r\n return this.iconSize() || 'sm';\r\n }\r\n\r\n // Methods\r\n toggleDropdown(): void {\r\n if (this.isComponentDisabled()) {\r\n return;\r\n }\r\n\r\n this.isOpen ? this.closeDropdown() : this.openDropdown();\r\n }\r\n\r\n openDropdown(): void {\r\n this.onTouched();\r\n\r\n if (this.overlayRef) {\r\n return;\r\n }\r\n\r\n // Check both the provided isFullscreenSelection and the screen size\r\n // If either is true, use fullscreen mode\r\n const shouldUseFullscreen = this.isFullscreenSelection() || this.responsiveService.isExtraSmallScreen();\r\n \r\n // Different overlay creation strategy based on mode\r\n if (shouldUseFullscreen) {\r\n this.openFullscreenDropdown();\r\n } else {\r\n this.openRegularDropdown();\r\n }\r\n\r\n this.isOpen = true;\r\n }\r\n\r\n private openRegularDropdown(): void {\r\n const positionStrategy = this.overlay\r\n .position()\r\n .flexibleConnectedTo(this.dropdownTrigger)\r\n .withPositions([\r\n {\r\n originX: 'start',\r\n originY: 'bottom',\r\n overlayX: 'start',\r\n overlayY: 'top',\r\n offsetY: 4\r\n },\r\n {\r\n originX: 'start',\r\n originY: 'top',\r\n overlayX: 'start',\r\n overlayY: 'bottom',\r\n offsetY: -4\r\n }\r\n ]);\r\n\r\n // Get parent width for minimum width constraint\r\n const parentWidth = this.dropdownTrigger.nativeElement.offsetWidth;\r\n \r\n // Configure overlay options based on the useContentWidth setting\r\n const overlayConfig: any = {\r\n positionStrategy,\r\n minWidth: parentWidth, // Always ensure minimum width matches parent\r\n scrollStrategy: this.overlay.scrollStrategies.close(),\r\n hasBackdrop: true,\r\n backdropClass: 'cdk-overlay-transparent-backdrop',\r\n panelClass: ['dropdown-overlay-panel', 'dropdown-animated-panel']\r\n };\r\n \r\n // Only set fixed width if useContentWidth is false\r\n if (!this.useContentWidth()) {\r\n overlayConfig.width = parentWidth;\r\n }\r\n \r\n // Apply maxWidth if provided\r\n if (this.maxWidth()) {\r\n overlayConfig.maxWidth = this.maxWidth();\r\n }\r\n \r\n this.overlayRef = this.overlay.create(overlayConfig);\r\n\r\n // Apply entrance animation\r\n const overlayElement = this.overlayRef.overlayElement;\r\n overlayElement.style.opacity = '0';\r\n overlayElement.style.transform = 'translateY(-10px) scale(0.98)';\r\n\r\n this.attachOptionsComponent();\r\n\r\n // Run the entrance animation after component is attached\r\n requestAnimationFrame(() => {\r\n overlayElement.style.transition = 'opacity 200ms cubic-bezier(0.25, 0.8, 0.25, 1), transform 200ms cubic-bezier(0.25, 0.8, 0.25, 1)';\r\n overlayElement.style.opacity = '1';\r\n overlayElement.style.transform = 'translateY(0) scale(1)';\r\n });\r\n }\r\n\r\n private openFullscreenDropdown(): void {\r\n const positionStrategy = this.overlay.position()\r\n .global()\r\n .centerHorizontally()\r\n .bottom();\r\n\r\n this.overlayRef = this.overlay.create({\r\n positionStrategy,\r\n width: '100%',\r\n height: '75vh',\r\n scrollStrategy: this.overlay.scrollStrategies.block(),\r\n hasBackdrop: true,\r\n backdropClass: 'cdk-overlay-dark-backdrop',\r\n panelClass: ['dropdown-overlay-fullscreen', 'dropdown-animated-fullscreen']\r\n });\r\n\r\n // Apply fullscreen entrance animation - slide from bottom\r\n const overlayElement = this.overlayRef.overlayElement;\r\n overlayElement.style.opacity = '0';\r\n overlayElement.style.transform = 'translateY(100%)';\r\n\r\n // Add overflow hidden to prevent horizontal scrolling\r\n overlayElement.style.overflowX = 'hidden';\r\n\r\n this.attachOptionsComponent(true);\r\n\r\n // Run the entrance animation after component is attached\r\n requestAnimationFrame(() => {\r\n overlayElement.style.transition = 'opacity 300ms cubic-bezier(0.25, 0.8, 0.25, 1), transform 300ms cubic-bezier(0.25, 0.8, 0.25, 1)';\r\n overlayElement.style.opacity = '1';\r\n overlayElement.style.transform = 'translateY(0)';\r\n });\r\n }\r\n\r\n private attachOptionsComponent(isFullscreen = false): void {\r\n // Create and attach the dropdown options component\r\n const optionsPortal = new ComponentPortal(KcDropdownOptionsComponent);\r\n const optionsRef = this.overlayRef!.attach(optionsPortal);\r\n \r\n // Configure options component\r\n optionsRef.instance.options = this.options();\r\n optionsRef.instance.filteredOptions = [...this.options()];\r\n optionsRef.instance.selectedValue = this._value;\r\n optionsRef.instance.variant = this.variant();\r\n optionsRef.instance.optionsEllipsis = this.optionsEllipsis();\r\n optionsRef.instance.isFullscreenSelection = isFullscreen;\r\n \r\n // Set search-related properties\r\n optionsRef.instance.isSearchable = this.isSearchable();\r\n optionsRef.instance.searchField = this.searchField();\r\n \r\n // Pass the custom template\r\n optionsRef.instance.customTemplate = this.optionTemplate;\r\n \r\n // Handle option selection\r\n optionsRef.instance.optionSelected.subscribe((option: DropdownOption) => {\r\n this.setValue(option.value);\r\n this.closeDropdown();\r\n });\r\n \r\n // Handle close button click for fullscreen mode\r\n optionsRef.instance.closeRequested?.subscribe(() => {\r\n this.closeDropdown();\r\n });\r\n \r\n // Handle backdrop click to close dropdown\r\n this.overlayRef!.backdropClick().pipe(\r\n takeUntil(this.destroy$)\r\n ).subscribe(() => {\r\n this.closeDropdown();\r\n });\r\n }\r\n\r\n closeDropdown(): void {\r\n if (this.overlayRef) {\r\n // Apply exit animation based on mode\r\n const overlayElement = this.overlayRef.overlayElement;\r\n\r\n if (this.isFullscreenSelection()) {\r\n // Fullscreen exit animation - slide down\r\n overlayElement.style.transition = 'opacity 250ms cubic-bezier(0.4, 0.0, 0.2, 1), transform 250ms cubic-bezier(0.4, 0.0, 0.2, 1)';\r\n overlayElement.style.opacity = '0';\r\n overlayElement.style.transform = 'translateY(100%)';\r\n } else {\r\n // Regular exit animation\r\n overlayElement.style.transition = 'opacity 180ms cubic-bezier(0.4, 0.0, 0.2, 1), transform 180ms cubic-bezier(0.4, 0.0, 0.2, 1)';\r\n overlayElement.style.opacity = '0';\r\n overlayElement.style.transform = 'translateY(-10px) scale(0.98)';\r\n }\r\n\r\n // Dispose after animation completes\r\n setTimeout(() => {\r\n if (this.overlayRef) {\r\n this.overlayRef.dispose();\r\n this.overlayRef = null;\r\n }\r\n }, this.isFullscreenSelection() ? 250 : 180);\r\n }\r\n\r\n this.isOpen = false;\r\n }\r\n\r\n setValue(value: any): void {\r\n if (this._value !== value) {\r\n this._value = value;\r\n this.onChange(value);\r\n this.valueChange.emit(value);\r\n }\r\n }\r\n\r\n // ControlValueAccessor implementation\r\n writeValue(value: any): void {\r\n this._value = value;\r\n }\r\n\r\n registerOnChange(fn: any): void {\r\n this.onChange = fn;\r\n }\r\n\r\n registerOnTouched(fn: any): void {\r\n this.onTouched = fn;\r\n }\r\n\r\n setDisabledState(isDisabled: boolean): void {\r\n this._internalDisabled = isDisabled;\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n\r\n if (this.overlayRef) {\r\n this.overlayRef.dispose();\r\n }\r\n }\r\n}\r\n","<div\r\n class=\"dropdown-select\"\r\n [ngClass]=\"[\r\n size(),\r\n variant(),\r\n isComponentDisabled() ? 'disabled' : '',\r\n isFullWidth() ? 'full-width' : '',\r\n getTypographyClass()\r\n ]\"\r\n (click)=\"toggleDropdown()\"\r\n #dropdownTrigger>\r\n <div class=\"dropdown-value\">\r\n <kc-icon *ngIf=\"icon()\" [iconClass]=\"icon()\" [size]=\"getIconSize()\" class=\"dropdown-icon\"></kc-icon>\r\n <ng-container *ngIf=\"prefixTemplateRef\">\r\n <ng-container [ngTemplateOutlet]=\"prefixTemplateRef\"></ng-container>\r\n </ng-container>\r\n <span>{{ getDisplayValue() }}</span>\r\n </div>\r\n <div *ngIf=\"showToggleIcon()\" class=\"dropdown-arrow\" [class.open]=\"isOpen\">\r\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 10 6\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path d=\"M1 1L5 5L9 1\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </div>\r\n</div> ","import { Component, EventEmitter, Input, Output, TemplateRef, OnInit, OnDestroy, ContentChild } from '@angular/core';\nimport { CommonModule, NgClass } from '@angular/common';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\nimport { Subject, debounceTime, takeUntil } from 'rxjs';\nimport { DropdownOption } from '../../dropdown-select/dropdown-select.models';\nimport { debounceTime as rxjsDebounceTime, distinctUntilChanged } from 'rxjs/operators';\nimport { ComponentSize } from '../../../types/sizing.type';\nimport { KcCheckboxComponent } from '../../checkbox/checkbox.component';\nimport { KcButtonComponent } from '../../button/button.component';\nimport { trigger, state, style, animate, transition } from '@angular/animations';\n\n@Component({\n selector: 'kc-dropdown-multiselect-options',\n standalone: true,\n imports: [CommonModule, NgClass, ReactiveFormsModule, KcCheckboxComponent, KcButtonComponent],\n templateUrl: './dropdown-multiselect-options.component.html',\n styleUrls: ['./dropdown-multiselect-options.component.scss'],\n animations: [\n trigger('enterLeave', [\n state('visible', style({\n opacity: 1,\n overflow: 'hidden',\n maxWidth: '100px',\n maxHeight: '32px',\n margin: '0 0 0 8px'\n })),\n state('hidden', style({\n opacity: 0,\n overflow: 'hidden',\n maxWidth: '0px',\n maxHeight: '0px',\n margin: '0',\n padding: '0'\n })),\n transition('hidden => visible', [\n animate('300ms ease-in-out')\n ]),\n transition('visible => hidden', [\n animate('300ms ease-in-out')\n ])\n ]),\n trigger('headerAnimation', [\n state('void', style({\n opacity: 0,\n height: '0px',\n minHeight: '0px',\n padding: '0px 16px',\n overflow: 'hidden',\n borderBottomWidth: '0px'\n })),\n state('*', style({\n opacity: 1,\n height: '*',\n minHeight: '42px',\n padding: '12px 16px',\n overflow: 'hidden',\n borderBottomWidth: '1px'\n })),\n transition('void <=> *', [\n animate('200ms ease-in-out')\n ])\n ])\n ]\n})\nexport class KcDropdownMultiselectOptionsComponent implements OnInit, OnDestroy {\n options: DropdownOption[] = [];\n filteredOptions: DropdownOption[] = [];\n selectedValues: any[] = [];\n variant: string = 'secondary';\n optionsEllipsis: boolean = false;\n isFullscreenSelection: boolean = false;\n isSearchable: boolean = false;\n searchField: string = 'label';\n minSelection: number = 0;\n maxSelection: number = Infinity;\n customTemplate: TemplateRef<any> | null = null;\n @ContentChild('emptyStateTemplate') emptyStateTemplate: TemplateRef<any> | null = null;\n size: ComponentSize = 'md';\n \n searchControl = new FormControl('');\n private destroy$ = new Subject<void>();\n \n @Output() optionSelected = new EventEmitter<DropdownOption>();\n @Output() closeRequested = new EventEmitter<void>();\n @Output() searchChanged = new EventEmitter<string>();\n @Output() clearAllRequested = new EventEmitter<void>();\n\n ngOnInit(): void {\n this.filteredOptions = [...this.options];\n \n if (this.isSearchable) {\n this.searchControl.valueChanges.pipe(\n debounceTime(128),\n takeUntil(this.destroy$)\n ).subscribe(value => {\n const searchTerm = value || '';\n this.filterOptions(searchTerm);\n this.searchChanged.emit(searchTerm);\n });\n }\n }\n \n filterOptions(searchText: string): void {\n if (!searchText.trim()) {\n this.filteredOptions = [...this.options];\n return;\n }\n \n const searchLower = searchText.toLowerCase();\n this.filteredOptions = this.options.filter(option => {\n const field = this.searchField === 'label' ? option.label : \n (option as any)[this.searchField] || '';\n return String(field).toLowerCase().includes(searchLower);\n });\n }\n\n isSelected(option: DropdownOption): boolean {\n return this.selectedValues.includes(option.value);\n }\n\n selectOption(option: DropdownOption): void {\n this.optionSelected.emit(option);\n \n // Immediately update the local selection state for UI feedback\n const isCurrentlySelected = this.isSelected(option);\n if (isCurrentlySelected) {\n this.selectedValues = this.selectedValues.filter(val => val !== option.value);\n } else {\n if (this.selectedValues.length < this.maxSelection || this.maxSelection === Infinity) {\n this.selectedValues = [...this.selectedValues, option.value];\n }\n }\n }\n \n clearAll(): void {\n this.clearAllRequested.emit();\n // Immediately update the local state for visual feedback\n this.selectedValues = [];\n }\n \n closeFullscreen(): void {\n this.closeRequested.emit();\n }\n \n isMaxSelectionsReached(): boolean {\n return this.selectedValues.length >= this.maxSelection && this.maxSelection !== Infinity;\n }\n \n getTypographyClass(): string {\n const typographyMap: Record<ComponentSize, string> = {\n 'xs': 'typo-text-1',\n 'sm': 'typo-text-2',\n 'md': 'typo-text-3',\n 'lg': 'typo-text-4',\n 'xlg': 'typo-text-5'\n };\n \n return typographyMap[this.size];\n }\n \n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n} ","<div \n class=\"dropdown-options\" \n [ngClass]=\"[variant, isFullscreenSelection ? 'fullscreen' : '']\">\n \n <!-- Fullscreen header -->\n <div *ngIf=\"isFullscreenSelection\" class=\"fullscreen-header\">\n <div class=\"header-title typo-title-3\">Select Options</div>\n <button class=\"header-close\" (click)=\"closeFullscreen()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n \n <!-- Options actions container - only show when searchable or has selections -->\n <div *ngIf=\"isSearchable || selectedValues.length > 0\" \n [@headerAnimation]\n class=\"options-actions\">\n <!-- Search container (always visible) -->\n <div class=\"search-container\">\n <input \n *ngIf=\"isSearchable\"\n type=\"text\" \n class=\"search-input\"\n [formControl]=\"searchControl\"\n placeholder=\"Search...\"\n [ngClass]=\"getTypographyClass()\"\n autocomplete=\"off\">\n </div>\n \n <!-- Clear selections button with animation -->\n <div class=\"clear-all-btn-enter-exit\" [@enterLeave]=\"selectedValues.length > 0 ? 'visible' : 'hidden'\">\n <kc-button\n *ngIf=\"selectedValues.length > 0\" \n [text]=\"'Clear all'\"\n [role]=\"'neutral'\"\n [size]=\"'sm'\"\n (buttonClick)=\"clearAll()\"\n class=\"clear-all-btn\"\n ></kc-button>\n </div>\n </div>\n \n <!-- Options list with fixed height -->\n <div class=\"options-list-container\">\n <div class=\"options-list\">\n <ng-container *ngIf=\"filteredOptions.length > 0; else noResults\">\n <div \n *ngFor=\"let option of filteredOptions\"\n class=\"option-item\"\n [class.selected]=\"isSelected(option)\"\n [class.disabled]=\"!isSelected(option) && isMaxSelectionsReached()\"\n [class.ellipsis]=\"optionsEllipsis\"\n [ngClass]=\"getTypographyClass()\"\n (click)=\"!isMaxSelectionsReached() || isSelected(option) ? selectOption(option) : null\">\n \n <!-- Custom template if provided -->\n <ng-container *ngIf=\"customTemplate; else defaultTemplate\">\n <ng-container *ngTemplateOutlet=\"customTemplate; context: { \n $implicit: option, \n selected: isSelected(option)\n }\"></ng-container>\n </ng-container>\n \n <!-- Default template -->\n <ng-template #defaultTemplate>\n <kc-checkbox \n [isChecked]=\"isSelected(option)\"\n [size]=\"size\"\n [isDisabled]=\"!isSelected(option) && isMaxSelectionsReached()\"\n ></kc-checkbox>\n <span class=\"option-label\">{{ option.label }}</span>\n </ng-template>\n </div>\n </ng-container>\n \n <!-- No results message -->\n <ng-template #noResults>\n <div class=\"no-results\" [ngClass]=\"getTypographyClass()\">\n <!-- Use projected empty state template when provided -->\n <ng-content select=\"[emptyStateTemplate]\" *ngIf=\"emptyStateTemplate\"></ng-content>\n \n <!-- Default empty state template when no projection provided -->\n <ng-container *ngIf=\"!emptyStateTemplate\">\n <div class=\"typo-caption\">No options found</div>\n </ng-container>\n </div>\n </ng-template>\n </div>\n </div>\n \n <!-- Selection summary for fullscreen mode -->\n <div *ngIf=\"isFullscreenSelection\" class=\"selection-summary\">\n <div class=\"selection-info\">\n <span class=\"typo-text-2\" *ngIf=\"selectedValues.length > 0\">Selected: {{ selectedValues.length }}</span>\n <span *ngIf=\"maxSelection < 9999999\" class=\"typo-text-2\">(Max: {{ maxSelection }})</span>\n </div>\n <kc-button\n [text]=\"'Apply'\"\n [variant]=\"variant === 'tertiary' || variant === 'raw' ? 'primary' : (variant === 'primary' || variant === 'secondary' ? variant : 'primary')\"