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(pagination): add new pagination component #106

Merged
merged 34 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
18761f3
feat(pagination): implement basics based on flowbite (wip)
bence444 Dec 11, 2024
e6482df
feat(pagination): implement basics based on flowbite (wip)
bence444 Dec 11, 2024
cc20b83
feat(pagination): move button design to its own directive
bence444 Dec 12, 2024
6d48101
Merge branch 'pagination' of https://github.com/bence444/flowbite-ang…
bence444 Dec 12, 2024
9f2ca65
feat(pagination): implement basics based on flowbite (wip)
bence444 Dec 11, 2024
1bd73bd
feat(pagination): move button design to its own directive
bence444 Dec 12, 2024
6a77a41
feat(pagination): implement basics based on flowbite (wip)
bence444 Dec 11, 2024
b2c05d5
Merge branch 'pagination' of https://github.com/bence444/flowbite-ang…
bence444 Dec 19, 2024
a43f5cb
fix: merge conflict
bence444 Dec 19, 2024
1aa9da0
feat(pagination): add icons, style active button, jsdoc comments
bence444 Dec 19, 2024
cbaf247
docs(pagination): add NgDoc documentation
bence444 Dec 19, 2024
620f320
fix(pagination): remove duplicated provider registrations
bence444 Dec 19, 2024
2c083bf
fix(pagination): remove unused pageChange output
bence444 Dec 19, 2024
ea541d2
fix(pagination): fix demo component selector
bence444 Dec 19, 2024
a47e774
fix(pagination): correct first visible page
bence444 Dec 20, 2024
1ceabbe
feat(utils): add double chevron icon from `https://flowbite.com/icons/`
bence444 Dec 20, 2024
c92b724
feat(paginator): add customizable icons, size variables
bence444 Dec 20, 2024
15376cb
feat(pagination): add property providers
bence444 Dec 20, 2024
523363e
Merge branch 'main' into pagination
MGREMY Dec 28, 2024
3edf1c4
feat(pagination): remove directive & use flowbite-button
MGREMY Dec 29, 2024
e524102
Merge pull request #1 from MGREMY/pagination
bence444 Dec 30, 2024
cb31e76
chore(pagination): code style updates
bence444 Dec 30, 2024
338e5f3
chore(pagination): use `model` instead of `input`
bence444 Dec 30, 2024
970b79d
refactor(pagination): remove `nav` element, use root and rootClass
bence444 Dec 30, 2024
b914355
chore(pagination): code style
bence444 Dec 30, 2024
c983fca
feat(utils): add left/double left chevron icon
bence444 Dec 30, 2024
434bfa4
feat(pagination): use left sided icons instead of `rotate-180`
bence444 Dec 30, 2024
783c3ed
Merge branch 'main' into pagination
bence444 Dec 30, 2024
0ec0dfa
fix(pagination): place label before icon in next/last button
bence444 Dec 30, 2024
87b1887
refactor(pagination): update post update post comment
MGREMY Dec 30, 2024
021058b
refactor(pagination): fix doc attribute
MGREMY Dec 30, 2024
3947513
Merge pull request #2 from MGREMY/pagination
bence444 Dec 31, 2024
bd238c4
feat(pagination): possibility to use `totalPages` or `totalItems`
bence444 Dec 31, 2024
e8860f1
refactor(pagination): use `Array.from` instead of for loop
bence444 Dec 31, 2024
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
Next Next commit
feat(pagination): implement basics based on flowbite (wip)
  • Loading branch information
bence444 committed Dec 11, 2024
commit 18761f3b27cc50e8a07b73187eb7819d7928795f
13 changes: 13 additions & 0 deletions libs/flowbite-angular/core/flowbite.theme.init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -243,6 +248,10 @@ export function initFlowbite(): EnvironmentProviders {
provide: NavbarBrandThemeService,
useClass: NavbarBrandThemeService,
},
{
provide: PaginationThemeService,
useClass: PaginationThemeService,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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:

    {
      provide: PaginationThemeService,
      useClass: PaginationThemeService,
    },
    {
      provide: NavbarContentThemeService,
      useClass: NavbarContentThemeService,
    },
    // ... other services ...
-   {
-     provide: PaginationThemeService,
-     useClass: PaginationThemeService,
-   },
    {
      provide: PaginationButtonThemeService,
      useClass: PaginationButtonThemeService,
    },

Also applies to: 280-287

{
provide: NavbarContentThemeService,
useClass: NavbarContentThemeService,
Expand Down Expand Up @@ -375,6 +384,10 @@ export function initFlowbite(): EnvironmentProviders {
provide: FLOWBITE_NAVBAR_CONTENT_THEME_TOKEN,
useValue: navbarContentTheme,
},
{
provide: FLOWBITE_PAGINATION_THEME_TOKEN,
useValue: paginationTheme,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove duplicate pagination theme token registration

The FLOWBITE_PAGINATION_THEME_TOKEN is registered twice in the theme providers array. This could lead to unexpected behavior in the theme system.

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,
Expand Down
4 changes: 4 additions & 0 deletions libs/flowbite-angular/pagination/README.md
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`.
7 changes: 7 additions & 0 deletions libs/flowbite-angular/pagination/index.ts
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';
5 changes: 5 additions & 0 deletions libs/flowbite-angular/pagination/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "./index.ts"
}
}
179 changes: 179 additions & 0 deletions libs/flowbite-angular/pagination/pagination.component.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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The 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:

  • Previous page at first page
  • Next page at last page
  • Navigation within bounds
  • Page size changes
  • Total items changes

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());
}
}
38 changes: 38 additions & 0 deletions libs/flowbite-angular/pagination/pagination.theme.service.ts
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;
}
}
55 changes: 55 additions & 0 deletions libs/flowbite-angular/pagination/pagination.theme.ts
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`
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect JSDoc reference

The JSDoc comment incorrectly references NavbarComponent instead of PaginationComponent.

 /**
- * Theme definition for `NavbarComponent`
+ * Theme definition for `PaginationComponent`
  */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Theme definition for `NavbarComponent`
*/
/**
* Theme definition for `PaginationComponent`
*/

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;
}
1 change: 1 addition & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"flowbite-angular/indicator": ["libs/flowbite-angular/indicator/index.ts"],
"flowbite-angular/modal": ["libs/flowbite-angular/modal/index.ts"],
"flowbite-angular/navbar": ["libs/flowbite-angular/navbar/index.ts"],
"flowbite-angular/pagination": ["libs/flowbite-angular/pagination/index.ts"],
"flowbite-angular/router-link": ["libs/flowbite-angular/router-link/index.ts"],
"flowbite-angular/router-link-active": ["libs/flowbite-angular/router-link-active/index.ts"],
"flowbite-angular/sanitize-html": ["libs/flowbite-angular/sanitize-html/index.ts"],
Expand Down