# Pagination Component

A standalone pagination component with four variants: numbered pagination, detailed (range and page-size selector), load more button, and infinite scroll functionality.

**Selector:** `ccl-pagination`

## Quick Start

1. **Import the component:**
```typescript
import { PaginationComponent } from '@Crystal-Code-Labs/ccl-ui-components';
```

2. **Add to your component:**
```typescript
@Component({
  imports: [PaginationComponent],
  // ... rest of component
})
```

3. **Use in template:**
```html
<ccl-pagination 
  variant="numbers"
  [currentPage]="2"
  [totalPages]="10"
  (pageChange)="onPageChange($event)">
</ccl-pagination>
```

## Inputs

- `currentPage: number` – Current active page (default: 1)
- `totalPages?: number` – Total number of pages (optional for load-more/infinite-scroll)
- `totalItems?: number` – Total number of items (used by `variant="detailed"` to compute ranges)
- `pageSize: number` – Items per page (default: 10)
- `pageSizeOptions: number[]` – Options for the page-size selector (default: `[10,25,50,100]`)
- `variant: 'numbers' | 'detailed' | 'load-more' | 'infinite-scroll'` – Pagination type (default: 'numbers')
- `showFirstLast: boolean` – Show first/last page buttons (default: false)

## Outputs

- `pageChange: EventEmitter<number>` – Emitted when page changes
- `pageSizeChange: EventEmitter<number>` – Emitted when the page size changes (detailed variant)

## Variants

### Numbers Pagination
Standard numbered pagination with previous/next buttons and page numbers.

```html
<ccl-pagination 
  variant="numbers"
  [currentPage]="3"
  [totalPages]="10"
  [showFirstLast]="true"
  (pageChange)="onPageChange($event)">
</ccl-pagination>
```

### Load More Button
Simple "Load More" button for progressive loading.

```html
<ccl-pagination 
  variant="load-more"
  [currentPage]="1"
  (pageChange)="loadMoreItems($event)">
</ccl-pagination>
```

### Infinite Scroll
### Detailed Pagination
Shows range text (e.g., `1–10 of 45`), current page summary, and a records-per-page selector.

```html
<ccl-pagination 
  variant="detailed"
  [currentPage]="page"
  [totalItems]="totalItems"
  [pageSize]="pageSize"
  [pageSizeOptions]="[10,25,50,100]"
  (pageChange)="onPageChange($event)"
  (pageSizeChange)="onPageSizeChange($event)">
</ccl-pagination>
```

#### Customizing Prev/Next Buttons
You can override the Prev/Next button rendering by passing `TemplateRef`s.

```html
<ng-template #prevTpl let-disabled="disabled" let-click="click">
  <button class="ccl-pagination__btn" [disabled]="disabled" (click)="click()">‹</button>
  <!-- Or your own icon/text -->
  <!-- <button class="ccl-pagination__btn" [disabled]="disabled" (click)="click()"><svg>...</svg></button> -->
  
  
</ng-template>

<ng-template #nextTpl let-disabled="disabled" let-click="click">
  <button class="ccl-pagination__btn" [disabled]="disabled" (click)="click()">›</button>
</ng-template>

<ccl-pagination
  variant="detailed"
  [currentPage]="page"
  [totalItems]="totalItems"
  [pageSize]="pageSize"
  [prevButtonTemplate]="prevTpl"
  [nextButtonTemplate]="nextTpl"
  (pageChange)="onPageChange($event)"
  (pageSizeChange)="onPageSizeChange($event)">
</ccl-pagination>
```
Automatically loads more content when user scrolls near the bottom.

```html
<ccl-pagination 
  variant="infinite-scroll"
  [currentPage]="1"
  (pageChange)="loadMoreItems($event)">
</ccl-pagination>
```

## Complete Examples

### Basic Numbered Pagination
```typescript
import { Component } from '@angular/core';
import { PaginationComponent } from '@Crystal-Code-Labs/ccl-ui-components';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [PaginationComponent],
  template: `
    <div class="content">
      <!-- Your content here -->
      <div class="items">
        @for (item of currentItems; track item.name) {
          <div>{{ item.name }}</div>
        }
      </div>
      
      <!-- Pagination -->
      <ccl-pagination 
        variant="numbers"
        [currentPage]="currentPage"
        [totalPages]="totalPages"
        [showFirstLast]="true"
        (pageChange)="onPageChange($event)">
      </ccl-pagination>
    </div>
  `
})
export class ExampleComponent {
  currentPage = 1;
  totalPages = 10;
  currentItems: any[] = [];

  onPageChange(page: number) {
    this.currentPage = page;
    this.loadItems(page);
  }

  loadItems(page: number) {
    // Load items for the specified page
    // this.currentItems = this.getItemsForPage(page);
  }
}
```

### Load More Pattern
```typescript
@Component({
  selector: 'app-load-more',
  standalone: true,
  imports: [PaginationComponent],
  template: `
    <div class="content">
      <div class="items">
        @for (item of allItems; track item.name) {
          <div>{{ item.name }}</div>
        }
      </div>
      
      <ccl-pagination 
        variant="load-more"
        [currentPage]="currentPage"
        (pageChange)="loadMore()">
      </ccl-pagination>
    </div>
  `
})
export class LoadMoreComponent {
  currentPage = 1;
  allItems: any[] = [];

  loadMore() {
    this.currentPage++;
    // Load more items and append to allItems
    // this.allItems = [...this.allItems, ...newItems];
  }
}
```

### Infinite Scroll Pattern
```typescript
@Component({
  selector: 'app-infinite-scroll',
  standalone: true,
  imports: [PaginationComponent],
  template: `
    <div class="content">
      <div class="items">
        @for (item of allItems; track item.name) {
          <div>{{ item.name }}</div>
        }
      </div>
      
      <ccl-pagination 
        variant="infinite-scroll"
        [currentPage]="currentPage"
        (pageChange)="loadMore()">
      </ccl-pagination>
    </div>
  `
})
export class InfiniteScrollComponent {
  currentPage = 1;
  allItems: any[] = [];
  loading = false;

  loadMore() {
    if (this.loading) return;
    
    this.loading = true;
    this.currentPage++;
    
    // Load more items
    // this.loadItems().then(newItems => {
    //   this.allItems = [...this.allItems, ...newItems];
    //   this.loading = false;
    // });
  }
}
```

### DataTable with Pagination
Complete example showing how to integrate pagination with the DataTable component:

> **TypeScript Compatibility Note**: The DataTable component expects `Record<string, unknown>[]` for its rows input. Make sure your data interfaces extend `Record<string, unknown>` or use the `Record<string, unknown>[]` type directly to avoid TypeScript errors.

```typescript
import { Component, OnInit } from '@angular/core';
import { DataTableComponent, DataTableColumn } from '@Crystal-Code-Labs/ccl-ui-components';
import { PaginationComponent } from '@Crystal-Code-Labs/ccl-ui-components';

interface User extends Record<string, unknown> {
  id: number;
  name: string;
  email: string;
  role: string;
  department: string;
  status: 'active' | 'inactive';
}

@Component({
  selector: 'app-user-table',
  standalone: true,
  imports: [DataTableComponent, PaginationComponent],
  template: `
    <div class="user-management">
      <h2>User Management</h2>
      
      <!-- Data Table -->
      <ccl-datatable
        [columns]="columns"
        [rows]="currentPageData"
        [striped]="true"
        [bordered]="true">
      </ccl-datatable>
      
      <!-- Pagination -->
      <div class="pagination-container">
        <div class="pagination-info">
          Showing {{ getStartIndex() }} to {{ getEndIndex() }} of {{ totalItems }} users
        </div>
        
        <ccl-pagination 
          variant="numbers"
          [currentPage]="currentPage"
          [totalPages]="totalPages"
          [showFirstLast]="true"
          (pageChange)="onPageChange($event)">
        </ccl-pagination>
      </div>
    </div>
  `,
  styles: [`
    .user-management {
      padding: 1rem;
    }
    
    .pagination-container {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 1rem;
      padding: 0.5rem 0;
    }
    
    .pagination-info {
      font-size: 0.875rem;
      color: var(--color-text-secondary, #666);
    }
  `]
})
export class UserTableComponent implements OnInit {
  columns: DataTableColumn[] = [
    { id: 'name', label: 'Name', width: '25%' },
    { id: 'email', label: 'Email', width: '30%' },
    { id: 'role', label: 'Role', width: '15%' },
    { id: 'department', label: 'Department', width: '20%' },
    { id: 'status', label: 'Status', width: '10%' }
  ];

  allUsers: User[] = [];
  currentPageData: User[] = [];
  currentPage = 1;
  pageSize = 10;
  totalItems = 0;
  totalPages = 0;

  ngOnInit() {
    this.loadUsers();
  }

  loadUsers() {
    // Simulate API call
    this.allUsers = this.generateMockUsers(150); // 150 total users
    this.totalItems = this.allUsers.length;
    this.totalPages = Math.ceil(this.totalItems / this.pageSize);
    this.updateCurrentPageData();
  }

  onPageChange(page: number) {
    this.currentPage = page;
    this.updateCurrentPageData();
  }

  updateCurrentPageData() {
    const startIndex = (this.currentPage - 1) * this.pageSize;
    const endIndex = startIndex + this.pageSize;
    this.currentPageData = this.allUsers.slice(startIndex, endIndex);
  }

  getStartIndex(): number {
    return (this.currentPage - 1) * this.pageSize + 1;
  }

  getEndIndex(): number {
    const end = this.currentPage * this.pageSize;
    return Math.min(end, this.totalItems);
  }

  private generateMockUsers(count: number): User[] {
    const names = ['John Doe', 'Jane Smith', 'Bob Johnson', 'Alice Brown', 'Charlie Wilson'];
    const roles = ['Admin', 'User', 'Manager', 'Developer', 'Designer'];
    const departments = ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'];
    const statuses: ('active' | 'inactive')[] = ['active', 'inactive'];

    return Array.from({ length: count }, (_, i) => ({
      id: i + 1,
      name: names[i % names.length] + ` ${i + 1}`,
      email: `user${i + 1}@example.com`,
      role: roles[i % roles.length],
      department: departments[i % departments.length],
      status: statuses[i % 2]
    }));
  }
}
```

### DataTable with Load More
For large datasets where you want to progressively load more data:

```typescript
@Component({
  selector: 'app-load-more-table',
  standalone: true,
  imports: [DataTableComponent, PaginationComponent],
  template: `
    <div class="load-more-table">
      <h2>Large Dataset Table</h2>
      
      <ccl-datatable
        [columns]="columns"
        [rows]="loadedData"
        [striped]="true">
      </ccl-datatable>
      
      <div class="load-more-container">
        <div class="data-info">
          Loaded {{ loadedData.length }} of {{ totalItems }} items
        </div>
        
        <ccl-pagination 
          variant="load-more"
          [currentPage]="currentPage"
          (pageChange)="loadMoreData()">
        </ccl-pagination>
      </div>
    </div>
  `,
  styles: [`
    .load-more-container {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 1rem;
      padding: 1rem;
      background: var(--color-background-surface, #f8f9fa);
      border-radius: var(--radius-md, 6px);
    }
    
    .data-info {
      font-size: 0.875rem;
      color: var(--color-text-secondary, #666);
    }
  `]
})
export class LoadMoreTableComponent implements OnInit {
  columns: DataTableColumn[] = [
    { id: 'id', label: 'ID', width: '10%' },
    { id: 'product', label: 'Product', width: '40%' },
    { id: 'category', label: 'Category', width: '20%' },
    { id: 'price', label: 'Price', width: '15%' },
    { id: 'stock', label: 'Stock', width: '15%' }
  ];

  allData: Record<string, unknown>[] = [];
  loadedData: Record<string, unknown>[] = [];
  currentPage = 1;
  pageSize = 20;
  totalItems = 0;

  ngOnInit() {
    this.initializeData();
    this.loadMoreData();
  }

  initializeData() {
    // Generate large dataset
    this.allData = this.generateMockProducts(1000);
    this.totalItems = this.allData.length;
  }

  loadMoreData() {
    const startIndex = (this.currentPage - 1) * this.pageSize;
    const endIndex = startIndex + this.pageSize;
    const newData = this.allData.slice(startIndex, endIndex);
    
    this.loadedData = [...this.loadedData, ...newData];
    this.currentPage++;
  }

  private generateMockProducts(count: number): Record<string, unknown>[] {
    const products = ['Widget', 'Gadget', 'Tool', 'Device', 'Component'];
    const categories = ['Electronics', 'Tools', 'Accessories', 'Parts', 'Supplies'];
    
    return Array.from({ length: count }, (_, i) => ({
      id: i + 1,
      product: `${products[i % products.length]} ${i + 1}`,
      category: categories[i % categories.length],
      price: `$${(Math.random() * 1000 + 10).toFixed(2)}`,
      stock: Math.floor(Math.random() * 100)
    }));
  }
}
```

### Testing DataTable with Pagination
```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DataTableComponent, PaginationComponent } from '@Crystal-Code-Labs/ccl-ui-components';

describe('UserTableComponent', () => {
  let fixture: ComponentFixture<UserTableComponent>;
  let component: UserTableComponent;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [UserTableComponent, DataTableComponent, PaginationComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(UserTableComponent);
    component = fixture.componentInstance;
  });

  it('should display correct number of rows per page', () => {
    component.pageSize = 5;
    component.totalItems = 23;
    component.ngOnInit();
    fixture.detectChanges();

    const tableRows = fixture.debugElement.queryAll(By.css('tbody tr'));
    expect(tableRows.length).toBe(5); // First page should have 5 rows
  });

  it('should update table data when page changes', () => {
    component.pageSize = 3;
    component.totalItems = 10;
    component.ngOnInit();
    fixture.detectChanges();

    const initialRows = fixture.debugElement.queryAll(By.css('tbody tr'));
    expect(initialRows.length).toBe(3);

    // Click next page
    const nextBtn = fixture.debugElement.queryAll(By.css('.ccl-pagination__btn')).pop();
    nextBtn?.nativeElement.click();
    fixture.detectChanges();

    const updatedRows = fixture.debugElement.queryAll(By.css('tbody tr'));
    expect(updatedRows.length).toBe(3); // Still 3 rows, but different data
  });

  it('should show correct pagination info', () => {
    component.pageSize = 10;
    component.totalItems = 25;
    component.currentPage = 2;
    component.ngOnInit();
    fixture.detectChanges();

    const infoText = fixture.debugElement.query(By.css('.pagination-info')).nativeElement.textContent;
    expect(infoText).toContain('Showing 11 to 20 of 25 users');
  });

  it('should disable previous button on first page', () => {
    component.currentPage = 1;
    component.totalPages = 5;
    fixture.detectChanges();

    const prevBtn = fixture.debugElement.queryAll(By.css('.ccl-pagination__btn'))[0];
    expect(prevBtn.nativeElement.disabled).toBeTrue();
  });
});
```

## Testing

The pagination component includes comprehensive test coverage. Here's how to test it in your application:

### Basic Testing Setup
```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { PaginationComponent } from '@Crystal-Code-Labs/ccl-ui-components';

describe('PaginationComponent', () => {
  let fixture: ComponentFixture<PaginationComponent>;
  let component: PaginationComponent;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [PaginationComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(PaginationComponent);
    component = fixture.componentInstance;
  });

  it('should render numbered pagination', () => {
    component.variant = 'numbers';
    component.totalPages = 5;
    component.currentPage = 2;
    fixture.detectChanges();

    const buttons = fixture.debugElement.queryAll(By.css('.ccl-pagination__btn'));
    expect(buttons.length).toBe(7); // prev + 5 pages + next

    const activeBtn = buttons.find(btn =>
      btn.nativeElement.classList.contains('active')
    );
    expect(activeBtn?.nativeElement.textContent.trim()).toBe('2');
  });

  it('should emit pageChange when page is clicked', () => {
    component.variant = 'numbers';
    component.totalPages = 5;
    component.currentPage = 2;
    spyOn(component.pageChange, 'emit');
    fixture.detectChanges();

    const nextBtn = fixture.debugElement.queryAll(By.css('.ccl-pagination__btn')).pop();
    nextBtn?.nativeElement.click();

    expect(component.pageChange.emit).toHaveBeenCalledWith(3);
  });

  it('should disable previous button on first page', () => {
    component.variant = 'numbers';
    component.totalPages = 5;
    component.currentPage = 1;
    fixture.detectChanges();

    const prevBtn = fixture.debugElement.queryAll(By.css('.ccl-pagination__btn'))[0];
    expect(prevBtn.nativeElement.disabled).toBeTrue();
  });

  it('should render load more button', () => {
    component.variant = 'load-more';
    component.currentPage = 1;
    spyOn(component.pageChange, 'emit');
    fixture.detectChanges();

    const btn = fixture.debugElement.query(By.css('.ccl-pagination__btn'));
    expect(btn.nativeElement.textContent).toContain('Load More');

    btn.nativeElement.click();
    expect(component.pageChange.emit).toHaveBeenCalledWith(2);
  });

  it('should trigger loadMore on infinite scroll', () => {
    component.variant = 'infinite-scroll';
    component.currentPage = 1;
    spyOn(component, 'loadMore').and.callThrough();
    spyOn(component.pageChange, 'emit');
    fixture.detectChanges();

    // Simulate scroll to bottom
    spyOnProperty(document.body, 'offsetHeight').and.returnValue(1000);
    spyOnProperty(window, 'innerHeight').and.returnValue(800);
    spyOnProperty(window, 'scrollY').and.returnValue(250);

    window.dispatchEvent(new Event('scroll'));

    expect(component.loadMore).toHaveBeenCalled();
    expect(component.pageChange.emit).toHaveBeenCalledWith(2);
  });
});
```

### Testing in Your Component
```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { YourComponent } from './your-component';
import { PaginationComponent } from '@Crystal-Code-Labs/ccl-ui-components';

describe('YourComponent', () => {
  let fixture: ComponentFixture<YourComponent>;
  let component: YourComponent;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [YourComponent, PaginationComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(YourComponent);
    component = fixture.componentInstance;
  });

  it('should handle page changes', () => {
    spyOn(component, 'onPageChange');
    fixture.detectChanges();

    const pagination = fixture.debugElement.query(By.css('ccl-pagination'));
    const nextBtn = pagination.queryAll(By.css('.ccl-pagination__btn')).pop();
    
    nextBtn?.nativeElement.click();
    
    expect(component.onPageChange).toHaveBeenCalled();
  });

  it('should update current page when pagination changes', () => {
    component.currentPage = 1;
    fixture.detectChanges();

    component.onPageChange(3);
    
    expect(component.currentPage).toBe(3);
  });
});
```

## Styling

The pagination component uses design tokens for consistent styling:

```css
.ccl-pagination {
  display: flex;
  gap: var(--spacing-xs, 4px);
  justify-content: center;
  align-items: center;
  margin: var(--spacing-sm, 8px) 0;
}

.ccl-pagination__btn {
  padding: var(--spacing-xs, 4px) var(--spacing-sm, 8px);
  border: 1px solid var(--color-border, #ccc);
  background: var(--color-background, #fff);
  border-radius: var(--radius-sm, 4px);
  font-size: var(--font-size-sm, 14px);
  cursor: pointer;
  transition: all var(--motion-fast, 150ms) ease;
}

.ccl-pagination__btn:hover:not(.active):not(:disabled) {
  background: var(--color-primary-default, #0055ff);
  color: #fff;
  border-color: var(--color-primary-default, #0055ff);
}

.ccl-pagination__btn.active {
  background: var(--color-primary-default, #0055ff);
  color: #fff;
  border-color: var(--color-primary-default, #0055ff);
}

.ccl-pagination__btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
```

## Accessibility

- **ARIA Labels**: All buttons include proper `aria-label` attributes
- **Current Page**: Active page button has `aria-current="page"`
- **Navigation Role**: Uses `role="navigation"` with `aria-label="Pagination"`
- **Keyboard Navigation**: All buttons are focusable and clickable
- **Screen Reader Support**: Clear labels for all interactive elements

## Theming

Pagination automatically uses your theme's design tokens for consistent styling across different themes. The active page and hover states use the theme's primary color:

```css
/* Custom theme overrides */
/* Prefer :root in global stylesheets; :host works in component styles. */
:root, :host {
  --color-primary-default: #f97316;
  --color-border: #e5e7eb;
  --spacing-xs: 6px;
  --spacing-sm: 12px;
  --radius-sm: 6px;
}
```

> :root vs :host
> - Use `:root` in a global stylesheet like `styles.scss` so variables apply app-wide.
> - Use `:host` inside a component stylesheet (ViewEncapsulation) or real Shadow DOM.
> - Our distributed theme CSS now targets both for compatibility.
