Component de selección dinámico con opciones de editar, eliminar y agregar
Ayúdame a mejora mi select, quiero hacer que se pueda agregar una opciones para eliminar o editar de ser necesario, o sea que, sea dinámico, si tiene una barra de búsqueda por que no agregar una opción para agregar un a nueva cosa en caso de ser requerido, además de eliminar o editar ese elemento, puede ser util para cosas como categorías, nombres, etc, solo le agrego lo necesario, hazlo sin modificar el estilo: ```ts import { CommonModule } from '@angular/common'; import { Component, ElementRef, EventEmitter, forwardRef, HostListener, Output, ViewChild, input, Input } from '@angular/core'; import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Icon } from "@icon"; import { trigger, transition, style, animate, state } from '@angular/animations'; export interface SelectOption { value: string | number; label: string; disabled?: boolean; } const DROPDOWN_ANIMATIONS = trigger('dropdownAnimation', [ state('void', style({ opacity: 0, transform: 'translateY( 8px)', height: 0 })), state('*', style({ opacity: 1, transform: 'translateY(0)', height: '*' })), transition('void <=> *', animate('200ms cubic bezier(0.4, 0, 0.2, 1)')) ]); @Component({ selector: 'app select', imports: [ CommonModule, FormsModule, Icon ], templateUrl: './select.component.html', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SelectComponent), multi: true } ], animations: [DROPDOWN_ANIMATIONS] }) export class SelectComponent implements ControlValueAccessor { readonly options = input<SelectOption[]>([]); readonly label = input<string>(''); // @Input() label = ''; readonly placeholder = input('Seleccione una opción'); readonly showSearch = input(false); readonly hasError = input(false); @Output() selectionChange = new EventEmitter<string | number>(); @ViewChild('selectContainer') selectContainer!: ElementRef; isOpen = false; value: string | number = ''; selectedOption: SelectOption | null = null; searchTerm = ''; filteredOptions: SelectOption[] = []; private onChange: (value: any) => void = () => { }; private onTouched: () => void = () => { }; constructor(private elementRef: ElementRef) { } ngOnInit() { this.filteredOptions = [...this.options()]; } @HostListener('document:click', ['$event']) onClickOutside(event: Event) { if (!this.selectContainer.nativeElement.contains(event.target)) { this.isOpen = false; } } toggleDropdown() { this.isOpen = !this.isOpen; if (this.isOpen) { this.searchTerm = ''; this.filteredOptions = [...this.options()]; this.onTouched(); } } selectOption(option: SelectOption) { this.value = option.value; this.selectedOption = option; this.onChange(this.value); this.selectionChange.emit(this.value); this.isOpen = false; } onSearch() { this.filteredOptions = this.options().filter(option => option.label.toLowerCase().includes(this.searchTerm.toLowerCase()) ); } // ControlValueAccessor Implementation writeValue(value: string | number): void { this.value = value; this.selectedOption = this.options().find(opt => opt.value === value) || null; } registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; } setDisabledState(isDisabled: boolean): void { // Implementar si se necesita deshabilitar el select } } ``` ```html <div class="relative w full" #selectContainer> <! Label > @if (label !== null) { <label class="block text sm font medium text black mb 1"> {{ label() }} </label> } <! Select Button > <button type="button" class="relative w full bg gray 50 border border gray 300 text gray 600 rounded md py 2.5 pl 3 pr 10 text left cursor default focus:outline none focus:ring 1 focus:ring gray 900 focus:border gray 900 sm:text sm" [class.border red 500]="hasError()" [class.border gray 300]="!hasError()" (click)="toggleDropdown()" aria haspopup="listbox" [attr.aria expanded]="isOpen" [attr.aria labelledby]="label()"> <span class="block truncate"> {{ selectedOption?.label || placeholder() }} </span> <span class="absolute inset y 0 right 0 flex items center pr 2 pointer events none"> <icon [name]="isOpen ? 'arrow up 01' : 'arrow down 01'" class="h 5 w 5 text gray 400" /> </span> </button> <! Dropdown > @if (isOpen) { <div class="absolute z 50 w full mt 4 bg white rounded md bshadow3 max h 60 overflow auto focus:outline none sm:text sm" [@dropdownAnimation]> <! Search Input > @if (showSearch()) { <div class="sticky top 0 p 2 bg white border b border gray 200"> <div class="relative"> <icon name="search" class="absolute left 3 top 1/2 translate y 1/2 h 4 w 4 text gray 400" /> <input type="text" class="w full pl 9 pr 3 py 2 border border gray 300 rounded md focus:outline none focus:ring 1 focus:ring gray 900 focus:border gray 900 text sm" placeholder="Buscar..." [(ngModel)]="searchTerm" (ngModelChange)="onSearch()" (click)="$event.stopPropagation()"> </div> </div> } <! Options List > <ul class="py 1" role="listbox"> @for (option of filteredOptions; track option.value) { <li class="relative py 2 pl 3 pr 9 cursor pointer select none" [class.bg gray 100]="option.value === value" [class.text gray 900]="option.value === value" [class.hover:bg gray 50]="!option.disabled" [class.text gray 400]="option.disabled" [class.cursor not allowed]="option.disabled" (click)="!option.disabled && selectOption(option)" role="option" [attr.aria selected]="option.value === value"> <span class="block truncate" [class.font medium]="option.value === value"> {{ option.label }} </span> @if (option.value === value) { <span class="absolute inset y 0 right 0 flex items center pr 4 text gray 900"> <icon name="tick" class="h 4 w 4" /> </span> } </li> } @empty { <li class="relative py 2 pl 3 pr 9 text gray 500 text center"> No se encontraron resultados </li> } </ul> </div> } </div> ```
