From c69456c2d3643c4918e9a7616aa76cf83f9f10c4 Mon Sep 17 00:00:00 2001 From: Anas Qureshi Date: Mon, 27 Jul 2026 12:46:03 +0530 Subject: [PATCH] new changes filter section add new ui --- .../states/pages/state-list/state-list.html | 73 ++++----- .../states/pages/state-list/state-list.scss | 4 + .../states/pages/state-list/state-list.ts | 10 +- .../components/data-table/data-table.html | 31 +++- .../components/data-table/data-table.scss | 153 ++++++++++++++++++ .../components/data-table/data-table.ts | 41 ++++- .../data-table-filter-actions.directive.ts | 15 ++ 7 files changed, 280 insertions(+), 47 deletions(-) create mode 100644 src/app/shared/directives/data-table-filter-actions/data-table-filter-actions.directive.ts diff --git a/src/app/features/global-masters/states/pages/state-list/state-list.html b/src/app/features/global-masters/states/pages/state-list/state-list.html index 0728e910..6d013bce 100644 --- a/src/app/features/global-masters/states/pages/state-list/state-list.html +++ b/src/app/features/global-masters/states/pages/state-list/state-list.html @@ -1,46 +1,41 @@ -
-
- -
-
- -
-
- -
-
-
-
-
- + (addClicked)="onAddState()" (filterClicked)="onToggleFilters()" (searchChanged)="onSearch($event)" (pageChanged)="onPageChange($event)" + (sortChanged)="onSortChange($event)" (actionClicked)="onActionClick($event)" toolTip="Add State"> + +
+
+ +
+
+
+ + + +
('create'); @@ -259,6 +261,10 @@ export class StateList { this.stateQueryRequests$.next(query); } + onToggleFilters(): void { + this.showFilters.update(value => !value); + } + onCountryLookupSelected(country: CountryLookupDto): void { this.selectedCountryLookup.set(country); } diff --git a/src/app/shared/components/data-table/data-table.html b/src/app/shared/components/data-table/data-table.html index 1a65ea66..9c3237f4 100644 --- a/src/app/shared/components/data-table/data-table.html +++ b/src/app/shared/components/data-table/data-table.html @@ -7,6 +7,13 @@ {{ tableTitle () }}
+ @if(showFilterButton()){ +
+ +
+ } @if(showAddButton()){
} - @if (showSearch()) {
@@ -30,6 +32,25 @@
+ @if (toolbarTemplate(); as toolbar) { +
+
+
+ + @if (filterActionsTemplate(); as filterActions) { +
+ +
+ } +
+ +
+
+ } +
diff --git a/src/app/shared/components/data-table/data-table.scss b/src/app/shared/components/data-table/data-table.scss index 305cef3f..d95ea149 100644 --- a/src/app/shared/components/data-table/data-table.scss +++ b/src/app/shared/components/data-table/data-table.scss @@ -35,6 +35,159 @@ color: var(--color-defaulttextcolor); } +/* Filter toggle button (in the toolbar) */ +.filter-btn-enter { + animation: filterButtonIn 0.2s ease-out; +} + +.filter-btn-leave { + animation: filterButtonOut 0.15s ease-in forwards; +} + +@keyframes filterButtonIn { + from { + opacity: 0; + transform: scale(0.9); + } + + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes filterButtonOut { + from { + opacity: 1; + transform: scale(1); + } + + to { + opacity: 0; + transform: scale(0.9); + } +} + +/* Full-width filter row, joined to the table card */ +.table-filter-row { + display: grid; + grid-template-rows: 0fr; + border-bottom-width: 0; + background-color: color-mix(in srgb, var(--color-primary) 3%, transparent); + transition: grid-template-rows 0.38s cubic-bezier(0.4, 0, 0.2, 1), + border-bottom-width 0.38s cubic-bezier(0.4, 0, 0.2, 1); +} + +.table-filter-row.is-open { + grid-template-rows: 1fr; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: var(--color-defaultborder); +} + +:host-context(.dark) .table-filter-row { + border-color: color-mix(in srgb, #fff 10%, transparent); + background-color: color-mix(in srgb, #fff 3%, transparent); +} + +.table-filter-row-inner { + grid-row: 1; + grid-column: 1; + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0 1.25rem; + min-height: 0; + min-width: 0; + opacity: 0; + transition: opacity 0.28s ease 0.06s, padding 0.38s cubic-bezier(0.4, 0, 0.2, 1); + align-items: baseline; +} + +.table-filter-row:not(.is-open) .table-filter-row-inner { + overflow: hidden; +} + +.table-filter-row.is-open .table-filter-row-inner { + padding: 0.875rem 1.25rem; + opacity: 1; + transition: opacity 0.32s ease 0.08s, padding 0.38s cubic-bezier(0.4, 0, 0.2, 1); +} + +.table-filter-row-content { + flex: 1 1 auto; + display: flex; + flex-wrap: nowrap; + align-items: flex-end; + gap: 0.75rem; + min-width: 0; + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 0.5rem; + margin-bottom: -0.5rem; + padding-top: 10px; + scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--color-primary) 40%, transparent) transparent; +} + +.table-filter-row-content>* { + flex: 0 0 auto; +} + +.table-filter-row-content::-webkit-scrollbar { + height: 2px; +} + +.table-filter-row-content::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--color-primary) 45%, transparent); + border-radius: 999px; + transition: background-color 0.2s ease; +} + +.table-filter-row-content:hover::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--color-primary) 65%, transparent); +} + +.table-filter-row-content::-webkit-scrollbar-track { + background: transparent; +} + +.table-filter-row-actions { + display: flex; + align-items: flex-end; + gap: 0.75rem; + flex: 0 0 auto; +} + +.table-filter-row-actions.is-pinned { + position: sticky; + right: 0; + margin-left: auto; + padding-left: 0.75rem; + background: linear-gradient(to right, transparent, color-mix(in srgb, var(--color-primary) 3%, var(--color-white)) 1rem); +} + +:host-context(.dark) .table-filter-row-actions.is-pinned { + background: linear-gradient(to right, transparent, color-mix(in srgb, #fff 3%, var(--color-bodybg)) 1rem); +} + +.table-filter-row .filter-close-btn { + margin-top: 0.125rem; +} + +.filter-close-btn { + transition: transform 0.15s ease, background-color 0.15s ease; + border-color: #7c3deb; +} + +.filter-close-btn:hover { + transform: rotate(90deg); +} + +.filter-close-btn:active { + transform: rotate(90deg) scale(0.9); +} + /* Search input */ .search-wrapper { position: relative; diff --git a/src/app/shared/components/data-table/data-table.ts b/src/app/shared/components/data-table/data-table.ts index dd96df3b..7e64f976 100644 --- a/src/app/shared/components/data-table/data-table.ts +++ b/src/app/shared/components/data-table/data-table.ts @@ -1,5 +1,5 @@ import { NgTemplateOutlet } from '@angular/common'; -import { Component, DestroyRef, HostListener, computed, contentChildren, effect, inject, input, output, signal } from '@angular/core'; +import { Component, DestroyRef, ElementRef, HostListener, computed, contentChildren, effect, inject, input, output, signal, viewChild } from '@angular/core'; import { MatPaginatorModule, PageEvent } from '@angular/material/paginator'; import { Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; @@ -13,6 +13,7 @@ import { Button } from '../button/button'; import { contentChild } from '@angular/core'; import { DataTableToolbarDirective } from '../../directives/data-table-toolbar/data-table-toolbar.directive'; +import { DataTableFilterActionsDirective } from '../../directives/data-table-filter-actions/data-table-filter-actions.directive'; @Component({ selector: 'app-data-table', @@ -33,6 +34,33 @@ export class DataTable { readonly cellTemplates = contentChildren(DataTableCellDirective); readonly toolbarTemplate = contentChild(DataTableToolbarDirective); + readonly filterActionsTemplate = contentChild(DataTableFilterActionsDirective); + + readonly filterRowContent = viewChild>('filterRowContent'); + readonly filterRowOverflowing = signal(false); + private filterRowResizeObserver?: ResizeObserver; + + private readonly filterRowContentEffect = effect(onCleanup => { + const element = this.filterRowContent()?.nativeElement; + + this.filterRowResizeObserver?.disconnect(); + + if (!element) { + this.filterRowOverflowing.set(false); + return; + } + + const checkOverflow = () => { + this.filterRowOverflowing.set(element.scrollWidth > element.clientWidth + 1); + }; + + checkOverflow(); + + this.filterRowResizeObserver = new ResizeObserver(checkOverflow); + this.filterRowResizeObserver.observe(element); + + onCleanup(() => this.filterRowResizeObserver?.disconnect()); + }); private readonly defaultRowClasses = [ 'table-primary', @@ -106,6 +134,8 @@ export class DataTable { showSearch = input(false); showformSelect = input(false); showAddButton = input(false); + showFilterButton = input(false); + filterActive = input(false); searchPlaceholder = input('Search...'); searchDebounceTime = input(300); emptyMessage = input('No records found'); @@ -141,6 +171,7 @@ export class DataTable { actionHeaderClass = input('!text-center'); actionCellClass = input('!text-center'); addClicked = output(); + filterClicked = output(); totalVisibleColumns = computed(() => this.columns().length + (this.actions().length > 0 ? 1 : 0)); @@ -299,6 +330,14 @@ export class DataTable { this.addClicked.emit(); } + onFilterClick(event: MouseEvent): void { + event.preventDefault(); + event.stopPropagation(); + + this.closeActionMenu(); + this.filterClicked.emit(); + } + toggleActionMenu(event: MouseEvent, row: T): void { event.stopPropagation(); diff --git a/src/app/shared/directives/data-table-filter-actions/data-table-filter-actions.directive.ts b/src/app/shared/directives/data-table-filter-actions/data-table-filter-actions.directive.ts new file mode 100644 index 00000000..3fab177b --- /dev/null +++ b/src/app/shared/directives/data-table-filter-actions/data-table-filter-actions.directive.ts @@ -0,0 +1,15 @@ +import { + Directive, + TemplateRef, + inject +} from '@angular/core'; + +@Directive({ + selector: '[appDataTableFilterActions]', + standalone: true +}) +export class DataTableFilterActionsDirective { + readonly templateRef = inject( + TemplateRef + ); +}