Merged PR 1843: feat(libs-shared-filter): show selected filter count on filter button

feat(libs-shared-filter): show selected filter count on filter button

- Display the number of selected filters as a badge on the filter menu button when filters are active.
- Add `.has-selected-filter` styling for visual emphasis when filters are selected.
- Update FilterService to provide a computed `selectedFilterCount` property, counting non-default filter inputs.
- Remove direct icon rendering from the button; icon is now handled by the button component.
- Update tests to mock and assert the new selected filter count logic.

Ref: #5070
This commit is contained in:
Nino Righi
2025-06-03 22:15:06 +00:00
committed by Lorenz Hilpert
parent bd7faeb1b5
commit bcd3c800b1
9 changed files with 67 additions and 24 deletions

View File

@@ -214,6 +214,37 @@ export class FilterService {
return isEqual(currentInputState, defaultInputState);
}
/**
* Computes the number of filter inputs that are currently selected (i.e., not in their default state).
*
* - For checkbox inputs, increments the count if any values are selected.
* - For date range inputs, increments the count if either start or stop is set.
* - Inputs in their default state are not counted.
*
* @remarks
* This property is a computed signal and updates automatically when the filter state changes.
*
* @returns The number of filter inputs that are currently selected and differ from their default state.
*/
selectedFilterCount = computed<number>(() => {
const currentState = getState(this.#state);
return currentState.inputs.reduce((count, input) => {
if (this.isDefaultFilterInput(input)) {
return count;
}
if (input.type === InputType.Checkbox && input.selected?.length) {
return count + 1;
}
if (input.type === InputType.DateRange && (input.start || input.stop)) {
return count + 1;
}
return count;
}, 0);
});
/**
* Indicates whether the current state is empty.
*/

View File

@@ -1,4 +1,7 @@
@let selected = selectedFilters();
<button
class="flex flex-row gap-2 items-center justify-center"
[class.has-selected-filter]="selected > 0"
uiIconButton
cdkOverlayOrigin
name="isaActionFilter"
@@ -8,7 +11,9 @@
[class.active]="isIconButtonActive()"
data-what="filter-button"
>
<ng-icon name="isaActionFilter"></ng-icon>
@if (selected > 0) {
({{ selected }})
}
</button>
<ng-template

View File

@@ -0,0 +1,3 @@
.has-selected-filter {
@apply w-full px-6;
}

View File

@@ -28,6 +28,7 @@ describe('FilterMenuButtonComponent', () => {
mockProvider(FilterService, {
isDefaultFilter: jest.fn().mockReturnValue(true),
rollback: jest.fn(),
selectedFilterCount: jest.fn().mockReturnValue(0),
}),
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],

View File

@@ -9,9 +9,7 @@ import {
output,
} from '@angular/core';
import { IconButtonComponent } from '@isa/ui/buttons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { FilterMenuComponent } from './filter-menu.component';
import { isaActionFilter } from '@isa/icons';
import { FilterService } from '../../core';
/**
@@ -23,13 +21,7 @@ import { FilterService } from '../../core';
templateUrl: './filter-menu-button.component.html',
styleUrls: ['./filter-menu-button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
IconButtonComponent,
OverlayModule,
NgIconComponent,
FilterMenuComponent,
],
providers: [provideIcons({ isaActionFilter })],
imports: [IconButtonComponent, OverlayModule, FilterMenuComponent],
})
export class FilterMenuButtonComponent {
scrollStrategy = inject(Overlay).scrollStrategies.block();
@@ -38,6 +30,8 @@ export class FilterMenuButtonComponent {
isIconButtonActive = computed(() => !this.#filter.isDefaultFilter());
selectedFilters = this.#filter.selectedFilterCount;
/**
* Tracks the open state of the filter menu.
*/