import { Component, input, output } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatSelectModule } from '@angular/material/select'; import { MatFormFieldModule } from '@angular/material/form-field'; import { PaginationState } from '../../../core/services/pagination.service'; /** * Pagination Component * Reusable pagination controls with customizable options * * Features: * - Previous/Next buttons * - First/Last page buttons * - Page number buttons with active state * - "Showing X-Y of Z results" display * - Page size selector * - Responsive design (fewer buttons on mobile) * - Accessible with keyboard navigation and ARIA labels * * @example * * */ @Component({ selector: 'app-pagination', imports: [ CommonModule, MatButtonModule, MatIconModule, MatTooltipModule, MatSelectModule, MatFormFieldModule ], template: `
Showing {{ state()!.startIndex }} to {{ state()!.endIndex }} of {{ state()!.totalItems }} {{ itemLabel() }}
@if (showPageSizeSelector()) { @for (option of pageSizeOptions(); track option) { {{ option }} per page } }
@if (showFirstLast()) { }
@for (page of pageNumbers(); track page) { @if (page === '...') { {{ page }} } @else { } }
@if (showFirstLast()) { }
`, styles: [` .pagination-container { display: flex; flex-direction: column; gap: 1rem; padding: 1rem; background: var(--surface-color, #fff); border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .pagination-info { display: flex; align-items: center; justify-content: center; } .info-text { font-size: 0.875rem; color: var(--text-secondary, #666); } .info-text strong { color: var(--text-primary, #333); font-weight: 600; } .pagination-actions { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; } .page-size-selector { min-width: 140px; } .page-size-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper { display: none; } .pagination-controls { display: flex; align-items: center; gap: 0.5rem; } .page-numbers { display: flex; align-items: center; gap: 0.25rem; } .page-button { min-width: 40px; height: 40px; padding: 0 8px; border-radius: 4px; transition: all 0.2s ease; } .page-button:hover { background-color: var(--hover-color, rgba(0, 0, 0, 0.04)); } .page-button.active { background-color: var(--primary-color, #1976d2); color: white; font-weight: 600; } .page-button.active:hover { background-color: var(--primary-dark, #1565c0); } .ellipsis { padding: 0 8px; color: var(--text-secondary, #666); user-select: none; } /* Responsive design */ @media (max-width: 768px) { .pagination-container { padding: 0.75rem; } .pagination-actions { flex-direction: column; align-items: stretch; } .page-size-selector { width: 100%; } .pagination-controls { justify-content: center; } .page-numbers { gap: 0.125rem; } .page-button { min-width: 36px; height: 36px; font-size: 0.875rem; } /* Hide ellipsis on very small screens */ @media (max-width: 480px) { .ellipsis { display: none; } } } /* Dark mode support */ @media (prefers-color-scheme: dark) { .pagination-container { background: var(--surface-dark, #1e1e1e); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .info-text { color: var(--text-secondary-dark, #b0b0b0); } .info-text strong { color: var(--text-primary-dark, #e0e0e0); } .page-button:hover { background-color: rgba(255, 255, 255, 0.08); } .ellipsis { color: var(--text-secondary-dark, #b0b0b0); } } /* Focus styles for accessibility */ .page-button:focus-visible, button:focus-visible { outline: 2px solid var(--primary-color, #1976d2); outline-offset: 2px; } /* Disabled state */ button:disabled { opacity: 0.4; cursor: not-allowed; } `] }) export class PaginationComponent { // Input signals state = input.required(); pageNumbers = input<(number | string)[]>([]); pageSizeOptions = input([10, 25, 50, 100]); showPageSizeSelector = input(true); showFirstLast = input(true); maxVisiblePages = input(5); itemLabel = input('results'); // Output events pageChange = output(); pageSizeChange = output(); /** * Handle page change */ onPageChange(page: number): void { if (page >= 1 && page <= (this.state()?.totalPages ?? 1)) { this.pageChange.emit(page); } } /** * Handle page button click (for page numbers) */ handlePageClick(page: number | string): void { if (typeof page === 'number') { this.onPageChange(page); } } /** * Handle page size change */ onPageSizeChange(pageSize: number): void { this.pageSizeChange.emit(pageSize); } }