-
Notifications
You must be signed in to change notification settings - Fork 45
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(pagination): add new pagination
component
#106
Changes from 1 commit
18761f3
e6482df
cc20b83
6d48101
9f2ca65
1bd73bd
6a77a41
b2c05d5
a43f5cb
1aa9da0
cbaf247
620f320
2c083bf
ea541d2
a47e774
1ceabbe
c92b724
15376cb
523363e
3edf1c4
e524102
cb31e76
338e5f3
970b79d
b914355
c983fca
434bfa4
783c3ed
0ec0dfa
87b1887
021058b
3947513
bd238c4
e8860f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -118,6 +118,11 @@ import { | |
navbarToggleTheme, | ||
NavbarToggleThemeService, | ||
} from 'flowbite-angular/navbar'; | ||
import { | ||
FLOWBITE_PAGINATION_THEME_TOKEN, | ||
paginationTheme, | ||
PaginationThemeService, | ||
} from 'flowbite-angular/pagination'; | ||
import { | ||
FLOWBITE_SCROLL_TOP_THEME_TOKEN, | ||
scrollTopDefaultValueProvider, | ||
|
@@ -243,6 +248,10 @@ export function initFlowbite(): EnvironmentProviders { | |
provide: NavbarBrandThemeService, | ||
useClass: NavbarBrandThemeService, | ||
}, | ||
{ | ||
provide: PaginationThemeService, | ||
useClass: PaginationThemeService, | ||
}, | ||
{ | ||
provide: NavbarContentThemeService, | ||
useClass: NavbarContentThemeService, | ||
|
@@ -375,6 +384,10 @@ export function initFlowbite(): EnvironmentProviders { | |
provide: FLOWBITE_NAVBAR_CONTENT_THEME_TOKEN, | ||
useValue: navbarContentTheme, | ||
}, | ||
{ | ||
provide: FLOWBITE_PAGINATION_THEME_TOKEN, | ||
useValue: paginationTheme, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove duplicate pagination theme token registration The Apply this change to remove the duplicate registration: {
provide: FLOWBITE_PAGINATION_THEME_TOKEN,
useValue: paginationTheme,
},
{
provide: FLOWBITE_NAVBAR_ITEM_THEME_TOKEN,
useValue: navbarItemTheme,
},
// ... other themes ...
- {
- provide: FLOWBITE_PAGINATION_THEME_TOKEN,
- useValue: paginationTheme,
- },
{
provide: FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN,
useValue: paginationButtonTheme,
}, Also applies to: 420-427 |
||
{ | ||
provide: FLOWBITE_NAVBAR_ITEM_THEME_TOKEN, | ||
useValue: navbarItemTheme, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# flowbite-angular/pagination | ||
|
||
Secondary entry point of `flowbite-angular`. It can be used by importing from | ||
`flowbite-angular/pagination`. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export { PaginationComponent } from './pagination.component'; | ||
export type { PaginationProperties, PaginationClass, PaginationTheme } from './pagination.theme'; | ||
export { paginationTheme } from './pagination.theme'; | ||
export { | ||
PaginationThemeService, | ||
FLOWBITE_PAGINATION_THEME_TOKEN, | ||
} from './pagination.theme.service'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"lib": { | ||
"entryFile": "./index.ts" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import type { PaginationClass, PaginationNavigation } from './pagination.theme'; | ||
import { PaginationThemeService } from './pagination.theme.service'; | ||
|
||
import { BaseComponent } from 'flowbite-angular'; | ||
|
||
import { | ||
ChangeDetectionStrategy, | ||
Component, | ||
computed, | ||
inject, | ||
input, | ||
model, | ||
ViewEncapsulation, | ||
} from '@angular/core'; | ||
|
||
@Component({ | ||
selector: 'flowbite-pagination', | ||
standalone: true, | ||
template: `<nav [class]="contentClasses().navigationClass"> | ||
@if (firstLast()) { | ||
<button | ||
type="button" | ||
(click)="firstPage()" | ||
[class]="contentClasses().listItemClass"> | ||
@switch (navigation()) { | ||
@case ('icon') { | ||
<< | ||
} | ||
@case ('text') { | ||
First | ||
} | ||
@case ('both') { | ||
<< First | ||
} | ||
} | ||
</button> | ||
} | ||
|
||
@if (prevNext()) { | ||
<button | ||
type="button" | ||
(click)="previousPage()" | ||
[class]="contentClasses().listItemClass"> | ||
@switch (navigation()) { | ||
@case ('icon') { | ||
< | ||
} | ||
@case ('text') { | ||
Previous | ||
} | ||
@case ('both') { | ||
< Previous | ||
} | ||
} | ||
</button> | ||
} | ||
|
||
@for (page of visiblePages(); track $index) { | ||
<button | ||
type="button" | ||
(click)="changePage(page)" | ||
[class]="contentClasses().listItemClass"> | ||
{{ page }} | ||
</button> | ||
} | ||
|
||
@if (prevNext()) { | ||
<button | ||
type="button" | ||
(click)="nextPage()" | ||
[class]="contentClasses().listItemClass"> | ||
@switch (navigation()) { | ||
@case ('icon') { | ||
> | ||
} | ||
@case ('text') { | ||
Next | ||
} | ||
@case ('both') { | ||
Next > | ||
} | ||
} | ||
</button> | ||
} | ||
|
||
@if (firstLast()) { | ||
<button | ||
type="button" | ||
(click)="lastPage()" | ||
[class]="contentClasses().listItemClass"> | ||
@switch (navigation()) { | ||
@case ('icon') { | ||
>> | ||
} | ||
@case ('text') { | ||
Last | ||
} | ||
@case ('both') { | ||
Last >> | ||
} | ||
} | ||
</button> | ||
} | ||
</nav>`, | ||
encapsulation: ViewEncapsulation.None, | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class PaginationComponent extends BaseComponent<PaginationClass> { | ||
private readonly _themeService = inject(PaginationThemeService); | ||
|
||
readonly currentPage = model.required<number>(); | ||
readonly totalItems = input.required<number>(); | ||
readonly tabs = input(5); | ||
readonly pageSize = input(25); | ||
readonly prevNext = input(true); | ||
readonly firstLast = input(true); | ||
readonly navigation = input<keyof PaginationNavigation>('icon'); | ||
|
||
private readonly _firstPage = computed(() => { | ||
if (this.currentPage() <= Math.floor(this.tabs() / 2)) { | ||
return 1; | ||
} | ||
|
||
if (this.currentPage() > this._maxPages() - Math.floor(this.tabs() / 2)) { | ||
return this._maxPages() - this.tabs() + 1; | ||
} | ||
|
||
return this.currentPage() - Math.floor(this.tabs() / 2); | ||
}); | ||
|
||
private readonly _maxPages = computed(() => { | ||
return Math.max(Math.floor(this.totalItems() / this.pageSize()), 1); | ||
}); | ||
|
||
readonly visiblePages = computed(() => { | ||
const pages: number[] = []; | ||
const visibleTabs = Math.min(this.tabs(), this._maxPages()); | ||
|
||
for (let i = this._firstPage(); i < this._firstPage() + visibleTabs; i++) { | ||
pages.push(i); | ||
} | ||
|
||
return pages; | ||
}); | ||
|
||
readonly visibleCurrentPage = computed(() => { | ||
return Math.min(this.currentPage(), this._maxPages()); | ||
}); | ||
|
||
public override fetchClass(): PaginationClass { | ||
return this._themeService.getClasses({ | ||
customStyle: {}, | ||
}); | ||
} | ||
|
||
changePage(page: number) { | ||
this.currentPage.set(page); | ||
} | ||
|
||
previousPage() { | ||
if (this.visibleCurrentPage() === 1) return; | ||
this.currentPage.update((value) => value - 1); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add unit tests for navigation methods. The navigation methods need test coverage to ensure they handle edge cases correctly. Consider adding these test cases:
Also applies to: 419-422 |
||
|
||
nextPage() { | ||
if (this.visibleCurrentPage() === this._maxPages()) return; | ||
this.currentPage.update((value) => value + 1); | ||
} | ||
|
||
firstPage() { | ||
if (this.currentPage() === this._maxPages()) return; | ||
this.currentPage.set(1); | ||
} | ||
|
||
lastPage() { | ||
if (this.currentPage() === this._maxPages()) return; | ||
this.currentPage.set(this._maxPages()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { PaginationClass, PaginationProperties, PaginationTheme } from './pagination.theme'; | ||
|
||
import type { FlowbiteThemeService } from 'flowbite-angular'; | ||
import { mergeTheme } from 'flowbite-angular/utils'; | ||
|
||
import { inject, Injectable, InjectionToken } from '@angular/core'; | ||
import { twMerge } from 'tailwind-merge'; | ||
|
||
/** | ||
* `InjectionToken` used to import `PaginationTheme` value | ||
* | ||
* @example | ||
* ``` | ||
* var theme = inject(FLOWBITE_PAGINATION_THEME) | ||
* ``` | ||
*/ | ||
export const FLOWBITE_PAGINATION_THEME_TOKEN = new InjectionToken<PaginationTheme>( | ||
'FLOWBITE_PAGINATION_THEME' | ||
); | ||
|
||
@Injectable({ | ||
providedIn: 'root', | ||
}) | ||
export class PaginationThemeService implements FlowbiteThemeService<PaginationProperties> { | ||
private readonly baseTheme = inject(FLOWBITE_PAGINATION_THEME_TOKEN); | ||
|
||
public getClasses(properties: PaginationProperties): PaginationClass { | ||
const theme: PaginationTheme = mergeTheme(this.baseTheme, properties.customStyle); | ||
|
||
const output: PaginationClass = { | ||
rootClass: twMerge(theme.root.base), | ||
navigationClass: twMerge(theme.navigation.base), | ||
listItemClass: twMerge(theme.listItem.base), | ||
}; | ||
|
||
return output; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,55 @@ | ||||||||||||||
import type { DeepPartial, FlowbiteClass } from 'flowbite-angular'; | ||||||||||||||
import { createTheme } from 'flowbite-angular/utils'; | ||||||||||||||
|
||||||||||||||
export interface PaginationNavigation { | ||||||||||||||
icon: string; | ||||||||||||||
text: string; | ||||||||||||||
both: string; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Required properties for class generation of `PaginationComponent` | ||||||||||||||
*/ | ||||||||||||||
export interface PaginationProperties { | ||||||||||||||
customStyle: DeepPartial<PaginationTheme>; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Theme definition for `NavbarComponent` | ||||||||||||||
*/ | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix incorrect JSDoc reference The JSDoc comment incorrectly references /**
- * Theme definition for `NavbarComponent`
+ * Theme definition for `PaginationComponent`
*/ 📝 Committable suggestion
Suggested change
|
||||||||||||||
export interface PaginationTheme { | ||||||||||||||
root: { | ||||||||||||||
base: string; | ||||||||||||||
}; | ||||||||||||||
navigation: { | ||||||||||||||
base: string; | ||||||||||||||
}; | ||||||||||||||
listItem: { | ||||||||||||||
base: string; | ||||||||||||||
active: string; | ||||||||||||||
}; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Default theme for `PaginationComponent` | ||||||||||||||
*/ | ||||||||||||||
export const paginationTheme: PaginationTheme = createTheme({ | ||||||||||||||
root: { | ||||||||||||||
base: '', | ||||||||||||||
}, | ||||||||||||||
navigation: { | ||||||||||||||
base: 'inline-flex -space-x-px', | ||||||||||||||
}, | ||||||||||||||
listItem: { | ||||||||||||||
base: 'flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 bg-white border border-e-0 border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white first:rounded-l-lg last:rounded-r-lg', | ||||||||||||||
active: '', | ||||||||||||||
}, | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Generated class definition for `PaginationComponent` | ||||||||||||||
*/ | ||||||||||||||
export interface PaginationClass extends FlowbiteClass { | ||||||||||||||
navigationClass: string; | ||||||||||||||
listItemClass: string; | ||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate PaginationThemeService registration
The
PaginationThemeService
is registered twice in the service providers array. This could lead to unexpected behavior in the dependency injection system.Apply this change to remove the duplicate registration:
Also applies to: 280-287