/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Component, Input, Output, EventEmitter, computed } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-pagination', standalone: true, imports: [CommonModule], template: `
Showing {{ startItem() }} - {{ endItem() }} of {{ totalItems }} items
@for (page of pageNumbers(); track page) { @if (page === '...') { {{ page }} } @else { } }
`, styles: [` .pagination-container { display: flex; justify-content: space-between; align-items: center; padding: 1rem 0; flex-wrap: wrap; gap: 1rem; } .pagination-info { color: var(--witch-plum); font-size: 0.9rem; } .pagination-controls { display: flex; align-items: center; gap: 0.5rem; } .pagination-btn { padding: 0.25rem 0.75rem; border: 1px solid var(--witch-lavender); background: var(--witch-moon); color: var(--witch-purple); border-radius: 4px; cursor: pointer; transition: all 0.2s; min-width: 2.5rem; } .pagination-btn:hover:not(:disabled) { background: var(--witch-lavender); border-color: var(--witch-mauve); transform: translateY(-1px); } .pagination-btn:disabled { opacity: 0.5; cursor: not-allowed; } .pagination-btn.active { background: #8b6f47; color: white; border-color: #8b6f47; } .page-numbers { display: flex; align-items: center; gap: 0.25rem; } .page-number { min-width: 2.5rem; } .page-ellipsis { padding: 0 0.5rem; color: var(--witch-mauve); } .page-size-selector { display: flex; align-items: center; gap: 0.5rem; } .page-size-selector label { font-size: 0.9rem; color: var(--witch-plum); } .page-size-select { padding: 0.25rem 0.5rem; border: 2px solid var(--witch-lavender); border-radius: 4px; background: var(--witch-moon); color: var(--witch-purple); cursor: pointer; transition: border-color 0.2s; } .page-size-select:hover { border-color: var(--witch-mauve); } .page-size-select:focus { outline: none; border-color: var(--witch-rose); box-shadow: 0 0 0 3px rgba(168, 87, 126, 0.2); } @media (max-width: 768px) { .pagination-container { justify-content: center; } .pagination-controls { order: 2; width: 100%; justify-content: center; } .pagination-info { order: 1; width: 100%; text-align: center; } .page-size-selector { order: 3; width: 100%; justify-content: center; } } `] }) export class PaginationComponent { @Input() currentPage = 1; @Input() pageSize = 25; @Input() totalItems = 0; @Output() pageChange = new EventEmitter(); @Output() pageSizeChange = new EventEmitter(); totalPages = computed(() => Math.ceil(this.totalItems / this.pageSize)); startItem = computed(() => { if (this.totalItems === 0) return 0; return (this.currentPage - 1) * this.pageSize + 1; }); endItem = computed(() => { return Math.min(this.currentPage * this.pageSize, this.totalItems); }); pageNumbers = computed(() => { const total = this.totalPages(); const current = this.currentPage; const pages: (number | string)[] = []; if (total <= 7) { // Show all pages if 7 or fewer for (let i = 1; i <= total; i++) { pages.push(i); } } else { // Always show first page pages.push(1); if (current <= 3) { // Near the beginning for (let i = 2; i <= 5; i++) { pages.push(i); } pages.push('...'); pages.push(total); } else if (current >= total - 2) { // Near the end pages.push('...'); for (let i = total - 4; i <= total; i++) { pages.push(i); } } else { // In the middle pages.push('...'); pages.push(current - 1); pages.push(current); pages.push(current + 1); pages.push('...'); pages.push(total); } } return pages; }); onPageChange(page: number) { if (page >= 1 && page <= this.totalPages() && page !== this.currentPage) { this.pageChange.emit(page); } } onPageSizeChange(event: Event) { const target = event.target as HTMLSelectElement; const newSize = parseInt(target.value, 10); this.pageSizeChange.emit(newSize); } }