82 lines
3.1 KiB
TypeScript
82 lines
3.1 KiB
TypeScript
import { CommonModule } from '@angular/common';
|
|
import { Component, HostListener, computed, inject, input, output, signal } from '@angular/core';
|
|
import { UiService } from '../../core/services/ui.service';
|
|
import type { Category } from '../models';
|
|
|
|
@Component({
|
|
selector: 'app-category-picker',
|
|
standalone: true,
|
|
imports: [CommonModule],
|
|
template: `
|
|
<div class="dropdown w-100 position-relative ec-category-picker">
|
|
<button class="form-select text-start d-flex align-items-center justify-content-between gap-2" type="button" (click)="toggle($event)">
|
|
<span class="d-flex flex-wrap gap-2 align-items-center">
|
|
@if (selectedItems().length) {
|
|
@for (item of selectedItems(); track item.id) {
|
|
<span class="badge ec-picker-badge d-inline-flex align-items-center gap-1">
|
|
<span class="badge rounded-pill ec-picker-dot" [style.background]="item.color"> </span>
|
|
{{ item.name }}
|
|
</span>
|
|
}
|
|
} @else {
|
|
<span class="text-secondary">{{ placeholder() }}</span>
|
|
}
|
|
</span>
|
|
<span class="text-secondary small">{{ selectedItems().length ? selectedItems().length : '' }}</span>
|
|
</button>
|
|
|
|
@if (open()) {
|
|
<div class="dropdown-menu show w-100 p-2 shadow-sm ec-category-picker-menu">
|
|
<div class="d-grid gap-1" style="max-height: 18rem; overflow: auto;">
|
|
@for (item of items(); track item.id) {
|
|
<label class="dropdown-item rounded-2 d-flex align-items-center justify-content-between gap-3" (click)="$event.stopPropagation()">
|
|
<span class="d-flex align-items-center gap-2">
|
|
<input class="form-check-input m-0" type="checkbox" [checked]="isSelected(item.id)" (change)="toggleItem(item.id)" />
|
|
<span class="badge rounded-pill ec-picker-dot" [style.background]="item.color"> </span>
|
|
<span>{{ item.name }}</span>
|
|
</span>
|
|
@if (isSelected(item.id)) {
|
|
<span class="badge text-bg-success">{{ ui.t('common.selected') }}</span>
|
|
}
|
|
</label>
|
|
} @empty {
|
|
<div class="dropdown-item text-secondary">{{ ui.t('common.noCategories') }}</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
`
|
|
})
|
|
export class CategoryPickerComponent {
|
|
readonly ui = inject(UiService);
|
|
readonly items = input<Category[]>([]);
|
|
readonly selectedIds = input<string[]>([]);
|
|
readonly placeholder = input('');
|
|
readonly changed = output<string[]>();
|
|
|
|
readonly open = signal(false);
|
|
readonly selectedItems = computed(() => this.items().filter((item) => this.selectedIds().includes(item.id)));
|
|
|
|
toggle(event?: Event) {
|
|
event?.stopPropagation();
|
|
this.open.update((value) => !value);
|
|
}
|
|
|
|
isSelected(id: string) {
|
|
return this.selectedIds().includes(id);
|
|
}
|
|
|
|
toggleItem(id: string) {
|
|
const next = this.isSelected(id)
|
|
? this.selectedIds().filter((item) => item !== id)
|
|
: [...this.selectedIds(), id];
|
|
this.changed.emit(next);
|
|
}
|
|
|
|
@HostListener('document:click')
|
|
closeOnOutsideClick() {
|
|
this.open.set(false);
|
|
}
|
|
}
|