Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wr-pagination component #279

Merged
merged 3 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions projects/lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@
},
"./select/styles": {
"sass": "./select/styles/_index.scss"
},
"./pagination/styles": {
"sass": "./pagination/styles/_index.scss"
}
}
}
8 changes: 8 additions & 0 deletions projects/lib/pagination/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
*/

export * from './public-api';
5 changes: 5 additions & 0 deletions projects/lib/pagination/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "public-api.ts"
}
}
46 changes: 46 additions & 0 deletions projects/lib/pagination/pagination.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<div class="wr-pagination-container" [class]="'wr-pagination--' + position()">
@if (showTotal()) {
<span class="wr-pagination-total">
{{ currentRange() }}
</span>
}

<div class="wr-pagination-items">
<wr-btn
outlined
[disabled]="disabled() || currentPage() === 1"
(click)="onPageChange(currentPage() - 1)"
icon="arrow-back"
></wr-btn>

@for (page of pages(); track page) {
@if (page === '...') {
<span class="wr-pagination-ellipsis">...</span>
} @else {
<wr-btn
[outlined]="!isCurrentPage(page)"
[color]="isCurrentPage(page) ? 'primary' : 'medium'"
[disabled]="disabled()"
(click)="onPageChange(page)"
>
{{ page }}
</wr-btn>
}
}

<wr-btn
outlined
[disabled]="disabled() || currentPage() === totalPages()"
(click)="onPageChange(currentPage() + 1)"
icon="arrow-forward"
></wr-btn>
</div>

@if (showSizeChanger()) {
<wr-select [disabled]="disabled()" [ngModel]="pageSize()" (ngModelChange)="onPageSizeChange($event)">
@for (option of pageSizeOptions(); track option) {
<wr-option [value]="option" [label]="String(option)"></wr-option>
}
</wr-select>
}
</div>
135 changes: 135 additions & 0 deletions projects/lib/pagination/pagination.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
*/

import {
ChangeDetectionStrategy,
Component,
ViewEncapsulation,
booleanAttribute,
computed,
input,
model,
numberAttribute,
HostBinding,
} from '@angular/core';
import { FormsModule } from '@angular/forms';

import { WrButtonComponent } from 'ngwr/button';
import { provideWrIcons, arrowBack, arrowForward } from 'ngwr/icon';
import { WrSelectComponent } from 'ngwr/select';
import { WrOptionComponent } from 'ngwr/select/select-option.component';

import { WrPaginationPosition } from './pagination.types';

@Component({
selector: 'wr-pagination',
standalone: true,
imports: [WrButtonComponent, WrSelectComponent, WrOptionComponent, FormsModule],
templateUrl: './pagination.component.html',
styleUrl: './pagination.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
providers: [provideWrIcons([arrowBack, arrowForward])],
})
export class WrPaginationComponent {
currentPage = model<number>(1);
total = input<number, number>(0, { transform: numberAttribute });
pageSize = model<number>(10);
pageSizeOptions = input<number[]>([10, 20, 50, 100]);
showSizeChanger = input<boolean, boolean>(false, { transform: booleanAttribute });
showTotal = input<boolean, boolean>(false, { transform: booleanAttribute });
position = input<WrPaginationPosition>('start');
disabled = input<boolean, boolean>(false, { transform: booleanAttribute });
ofLabel = input<string>('of');

@HostBinding('class')
get hostClasses(): Record<string, boolean> {
return {
'wr-pagination': true,
'wr-pagination--disabled': this.disabled(),
};
}

protected readonly String = String;

protected readonly totalPages = computed(() => Math.ceil(this.total() / this.pageSize()));

protected readonly currentRange = computed(() => {
const start = (this.currentPage() - 1) * this.pageSize() + 1;
const end = Math.min(this.currentPage() * this.pageSize(), this.total());
return `${start}-${end} ${this.ofLabel()} ${this.total()}`;
});

protected readonly pages = computed(() => {
const total = this.totalPages();
const current = this.currentPage();
const items: (number | '...')[] = [];

if (total <= 7) {
return Array.from({ length: total }, (_, i) => i + 1);
}

items.push(1);

if (current > 4) {
items.push('...');
}

let start: number;
let end: number;

if (current <= 4) {
start = 2;
end = 5;
} else if (current >= total - 3) {
start = total - 4;
end = total - 1;
} else {
start = current - 2;
end = current + 2;
}

for (let i = start; i <= end; i++) {
items.push(i);
}

if (current < total - 3) {
items.push('...');
}

if (end !== total) {
items.push(total);
}

return items;
});

protected isCurrentPage(page: number): boolean {
return this.currentPage() === page;
}

protected onPageChange(page: number): void {
if (this.disabled() || page === this.currentPage() || page < 1 || page > this.totalPages()) {
return;
}

this.currentPage.set(page);
}

protected onPageSizeChange(size: number): void {
if (this.disabled() || size === this.pageSize()) {
return;
}

this.pageSize.set(size);

const newTotalPages = Math.ceil(this.total() / size);
if (this.currentPage() > newTotalPages) {
this.onPageChange(newTotalPages);
}
}
}
8 changes: 8 additions & 0 deletions projects/lib/pagination/pagination.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
*/

export type WrPaginationPosition = 'start' | 'center' | 'end';
9 changes: 9 additions & 0 deletions projects/lib/pagination/public-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
*/

export * from './pagination.types';
export * from './pagination.component';
60 changes: 60 additions & 0 deletions projects/lib/pagination/styles/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@forward "./vars";

.wr-pagination {
--wr-select-option-height: var(--wr-pagination-select-option-height);
--wr-select-padding-y: var(--wr-pagination-select-padding-y);
--wr-select-padding-x: var(--wr-pagination-select-padding-x);

display: block;

&-container {
display: flex;
align-items: center;
gap: var(--wr-pagination-gap);
}

&--start {
justify-content: flex-start;
}

&--center {
justify-content: center;
}

&--end {
justify-content: flex-end;
}

&--disabled {
opacity: 0.6;
pointer-events: none;
}

&-total {
color: var(--wr-pagination-total-color);
font-size: var(--wr-pagination-font-size);
}

&-items {
display: flex;
align-items: center;
gap: var(--wr-pagination-items-gap);
}

&-ellipsis {
color: var(--wr-pagination-ellipsis-color);
padding: 0 var(--wr-pagination-item-padding);
user-select: none;
}

wr-btn {
display: flex;
align-items: center;
justify-content: center;
min-width: var(--wr-pagination-item-size);
height: var(--wr-pagination-item-size);
padding: 0;
gap: 0;
text-align: center;
}
}
14 changes: 14 additions & 0 deletions projects/lib/pagination/styles/vars.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
:root {
--wr-pagination-gap: 1rem;
--wr-pagination-items-gap: 0.5rem;
--wr-pagination-font-size: 0.875rem;
--wr-pagination-total-color: var(--wr-color-medium);
--wr-pagination-ellipsis-color: var(--wr-color-medium);

--wr-pagination-item-size: 1.625rem;
--wr-pagination-item-padding: 0.5rem;

--wr-pagination-select-option-height: var(--wr-pagination-item-size);
--wr-pagination-select-padding-y: 0;
--wr-pagination-select-padding-x: 0.825rem;
}
1 change: 1 addition & 0 deletions projects/lib/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
@forward './tag/styles';
@forward './textarea/styles';
@forward './select/styles';
@forward './pagination/styles';
//@forward './tooltip';
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export class SidebarComponent extends WrAbstractBase implements OnInit {
{ title: 'Tag', url: [routes.docs.components.index, routes.docs.components.tag] },
{ title: 'Textarea', url: [routes.docs.components.index, routes.docs.components.textarea] },
{ title: 'Select', url: [routes.docs.components.index, routes.docs.components.select] },
{ title: 'Pagination', url: [routes.docs.components.index, routes.docs.components.pagination] },
],
},
];
Expand Down
4 changes: 4 additions & 0 deletions projects/showcase/app/docs/components/components.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,8 @@ export default [
path: components.select,
loadComponent: () => import('./select/select.component').then(c => c.SelectComponent),
},
{
path: components.pagination,
loadComponent: () => import('./pagination/pagination.component').then(c => c.PaginationComponent),
},
] satisfies Routes;
Loading