From 18761f3b27cc50e8a07b73187eb7819d7928795f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Wed, 11 Dec 2024 22:24:26 +0100 Subject: [PATCH 01/28] feat(pagination): implement basics based on flowbite (wip) --- .../core/flowbite.theme.init.ts | 13 ++ libs/flowbite-angular/pagination/README.md | 4 + libs/flowbite-angular/pagination/index.ts | 7 + .../pagination/ng-package.json | 5 + .../pagination/pagination.component.ts | 179 ++++++++++++++++++ .../pagination/pagination.theme.service.ts | 38 ++++ .../pagination/pagination.theme.ts | 55 ++++++ tsconfig.base.json | 1 + 8 files changed, 302 insertions(+) create mode 100644 libs/flowbite-angular/pagination/README.md create mode 100644 libs/flowbite-angular/pagination/index.ts create mode 100644 libs/flowbite-angular/pagination/ng-package.json create mode 100644 libs/flowbite-angular/pagination/pagination.component.ts create mode 100644 libs/flowbite-angular/pagination/pagination.theme.service.ts create mode 100644 libs/flowbite-angular/pagination/pagination.theme.ts diff --git a/libs/flowbite-angular/core/flowbite.theme.init.ts b/libs/flowbite-angular/core/flowbite.theme.init.ts index e73e8cf5..ea38a3f9 100644 --- a/libs/flowbite-angular/core/flowbite.theme.init.ts +++ b/libs/flowbite-angular/core/flowbite.theme.init.ts @@ -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, + }, { provide: FLOWBITE_NAVBAR_ITEM_THEME_TOKEN, useValue: navbarItemTheme, diff --git a/libs/flowbite-angular/pagination/README.md b/libs/flowbite-angular/pagination/README.md new file mode 100644 index 00000000..50e27d7e --- /dev/null +++ b/libs/flowbite-angular/pagination/README.md @@ -0,0 +1,4 @@ +# flowbite-angular/pagination + +Secondary entry point of `flowbite-angular`. It can be used by importing from +`flowbite-angular/pagination`. diff --git a/libs/flowbite-angular/pagination/index.ts b/libs/flowbite-angular/pagination/index.ts new file mode 100644 index 00000000..90544123 --- /dev/null +++ b/libs/flowbite-angular/pagination/index.ts @@ -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'; diff --git a/libs/flowbite-angular/pagination/ng-package.json b/libs/flowbite-angular/pagination/ng-package.json new file mode 100644 index 00000000..1f7f6a0d --- /dev/null +++ b/libs/flowbite-angular/pagination/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "./index.ts" + } +} diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts new file mode 100644 index 00000000..575f5ac2 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -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: ``, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PaginationComponent extends BaseComponent { + private readonly _themeService = inject(PaginationThemeService); + + readonly currentPage = model.required(); + readonly totalItems = input.required(); + readonly tabs = input(5); + readonly pageSize = input(25); + readonly prevNext = input(true); + readonly firstLast = input(true); + readonly navigation = input('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); + } + + 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()); + } +} diff --git a/libs/flowbite-angular/pagination/pagination.theme.service.ts b/libs/flowbite-angular/pagination/pagination.theme.service.ts new file mode 100644 index 00000000..5b68dc7c --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination.theme.service.ts @@ -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( + 'FLOWBITE_PAGINATION_THEME' +); + +@Injectable({ + providedIn: 'root', +}) +export class PaginationThemeService implements FlowbiteThemeService { + 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; + } +} diff --git a/libs/flowbite-angular/pagination/pagination.theme.ts b/libs/flowbite-angular/pagination/pagination.theme.ts new file mode 100644 index 00000000..076ee4c5 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination.theme.ts @@ -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; +} + +/** + * Theme definition for `NavbarComponent` + */ +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; +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 02f55fe4..7ff69ff2 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -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"], From e6482dfb5e4f49f0738c93deb7cd1e918abfa509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Wed, 11 Dec 2024 22:24:26 +0100 Subject: [PATCH 02/28] feat(pagination): implement basics based on flowbite (wip) --- .../core/flowbite.theme.init.ts | 13 ++ libs/flowbite-angular/pagination/README.md | 4 + libs/flowbite-angular/pagination/index.ts | 7 + .../pagination/ng-package.json | 5 + .../pagination/pagination.component.ts | 179 ++++++++++++++++++ .../pagination/pagination.theme.service.ts | 38 ++++ .../pagination/pagination.theme.ts | 55 ++++++ tsconfig.base.json | 1 + 8 files changed, 302 insertions(+) create mode 100644 libs/flowbite-angular/pagination/README.md create mode 100644 libs/flowbite-angular/pagination/index.ts create mode 100644 libs/flowbite-angular/pagination/ng-package.json create mode 100644 libs/flowbite-angular/pagination/pagination.component.ts create mode 100644 libs/flowbite-angular/pagination/pagination.theme.service.ts create mode 100644 libs/flowbite-angular/pagination/pagination.theme.ts diff --git a/libs/flowbite-angular/core/flowbite.theme.init.ts b/libs/flowbite-angular/core/flowbite.theme.init.ts index e73e8cf5..ea38a3f9 100644 --- a/libs/flowbite-angular/core/flowbite.theme.init.ts +++ b/libs/flowbite-angular/core/flowbite.theme.init.ts @@ -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, + }, { provide: FLOWBITE_NAVBAR_ITEM_THEME_TOKEN, useValue: navbarItemTheme, diff --git a/libs/flowbite-angular/pagination/README.md b/libs/flowbite-angular/pagination/README.md new file mode 100644 index 00000000..50e27d7e --- /dev/null +++ b/libs/flowbite-angular/pagination/README.md @@ -0,0 +1,4 @@ +# flowbite-angular/pagination + +Secondary entry point of `flowbite-angular`. It can be used by importing from +`flowbite-angular/pagination`. diff --git a/libs/flowbite-angular/pagination/index.ts b/libs/flowbite-angular/pagination/index.ts new file mode 100644 index 00000000..90544123 --- /dev/null +++ b/libs/flowbite-angular/pagination/index.ts @@ -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'; diff --git a/libs/flowbite-angular/pagination/ng-package.json b/libs/flowbite-angular/pagination/ng-package.json new file mode 100644 index 00000000..1f7f6a0d --- /dev/null +++ b/libs/flowbite-angular/pagination/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "./index.ts" + } +} diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts new file mode 100644 index 00000000..575f5ac2 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -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: ``, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PaginationComponent extends BaseComponent { + private readonly _themeService = inject(PaginationThemeService); + + readonly currentPage = model.required(); + readonly totalItems = input.required(); + readonly tabs = input(5); + readonly pageSize = input(25); + readonly prevNext = input(true); + readonly firstLast = input(true); + readonly navigation = input('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); + } + + 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()); + } +} diff --git a/libs/flowbite-angular/pagination/pagination.theme.service.ts b/libs/flowbite-angular/pagination/pagination.theme.service.ts new file mode 100644 index 00000000..5b68dc7c --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination.theme.service.ts @@ -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( + 'FLOWBITE_PAGINATION_THEME' +); + +@Injectable({ + providedIn: 'root', +}) +export class PaginationThemeService implements FlowbiteThemeService { + 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; + } +} diff --git a/libs/flowbite-angular/pagination/pagination.theme.ts b/libs/flowbite-angular/pagination/pagination.theme.ts new file mode 100644 index 00000000..076ee4c5 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination.theme.ts @@ -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; +} + +/** + * Theme definition for `NavbarComponent` + */ +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; +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 02f55fe4..7ff69ff2 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -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"], From cc20b8311d25d1e7660bb016efd345f8e0383724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Thu, 12 Dec 2024 23:03:43 +0100 Subject: [PATCH 03/28] feat(pagination): move button design to its own directive --- .../core/flowbite.theme.init.ts | 31 ++++++--- libs/flowbite-angular/pagination/index.ts | 21 +++++- .../pagination/pagination-button.directive.ts | 44 ++++++++++++ .../pagination-button.theme.service.ts | 42 ++++++++++++ .../pagination/pagination-button.theme.ts | 38 +++++++++++ .../pagination/pagination.component.ts | 68 +++++++++++++------ .../pagination/pagination.theme.service.ts | 3 +- .../pagination/pagination.theme.ts | 9 --- 8 files changed, 216 insertions(+), 40 deletions(-) create mode 100644 libs/flowbite-angular/pagination/pagination-button.directive.ts create mode 100644 libs/flowbite-angular/pagination/pagination-button.theme.service.ts create mode 100644 libs/flowbite-angular/pagination/pagination-button.theme.ts diff --git a/libs/flowbite-angular/core/flowbite.theme.init.ts b/libs/flowbite-angular/core/flowbite.theme.init.ts index ea38a3f9..c8f6cae4 100644 --- a/libs/flowbite-angular/core/flowbite.theme.init.ts +++ b/libs/flowbite-angular/core/flowbite.theme.init.ts @@ -119,7 +119,12 @@ import { NavbarToggleThemeService, } from 'flowbite-angular/navbar'; import { + FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN, FLOWBITE_PAGINATION_THEME_TOKEN, + paginationButtonDefaultValueProvider, + paginationButtonTheme, + PaginationButtonThemeService, + paginationDefaultValueProvider, paginationTheme, PaginationThemeService, } from 'flowbite-angular/pagination'; @@ -248,10 +253,6 @@ export function initFlowbite(): EnvironmentProviders { provide: NavbarBrandThemeService, useClass: NavbarBrandThemeService, }, - { - provide: PaginationThemeService, - useClass: PaginationThemeService, - }, { provide: NavbarContentThemeService, useClass: NavbarContentThemeService, @@ -272,6 +273,14 @@ export function initFlowbite(): EnvironmentProviders { provide: NavbarThemeService, useClass: NavbarThemeService, }, + { + provide: PaginationThemeService, + useClass: PaginationThemeService, + }, + { + provide: PaginationButtonThemeService, + useClass: PaginationButtonThemeService, + }, { provide: ScrollTopThemeService, useClass: ScrollTopThemeService, @@ -384,10 +393,6 @@ export function initFlowbite(): EnvironmentProviders { provide: FLOWBITE_NAVBAR_CONTENT_THEME_TOKEN, useValue: navbarContentTheme, }, - { - provide: FLOWBITE_PAGINATION_THEME_TOKEN, - useValue: paginationTheme, - }, { provide: FLOWBITE_NAVBAR_ITEM_THEME_TOKEN, useValue: navbarItemTheme, @@ -404,6 +409,14 @@ export function initFlowbite(): EnvironmentProviders { provide: FLOWBITE_NAVBAR_THEME_TOKEN, useValue: navbarTheme, }, + { + provide: FLOWBITE_PAGINATION_THEME_TOKEN, + useValue: paginationTheme, + }, + { + provide: FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN, + useValue: paginationButtonTheme, + }, { provide: FLOWBITE_SCROLL_TOP_THEME_TOKEN, useValue: scrollTopTheme, @@ -460,6 +473,8 @@ export function initFlowbite(): EnvironmentProviders { navbarIconButtonDefaultValueProvider, navbarContentDefaultValueProvider, navbarBrandDefaultThemeProvider, + paginationDefaultValueProvider, + paginationButtonDefaultValueProvider, scrollTopDefaultValueProvider, sidebarDefaultValueProvider, sidebarToggleDefaultValueProvider, diff --git a/libs/flowbite-angular/pagination/index.ts b/libs/flowbite-angular/pagination/index.ts index 90544123..008ffbdf 100644 --- a/libs/flowbite-angular/pagination/index.ts +++ b/libs/flowbite-angular/pagination/index.ts @@ -1,7 +1,26 @@ -export { PaginationComponent } from './pagination.component'; +export { + PaginationComponent, + paginationDefaultValueProvider, + FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE, +} 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'; + +export { + paginationButtonDefaultValueProvider, + FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE, +} from './pagination-button.directive'; +export type { + PaginationButtonProperties, + PaginationButtonClass, + PaginationButtonTheme, +} from './pagination-button.theme'; +export { paginationButtonTheme } from './pagination-button.theme'; +export { + PaginationButtonThemeService, + FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN, +} from './pagination-button.theme.service'; diff --git a/libs/flowbite-angular/pagination/pagination-button.directive.ts b/libs/flowbite-angular/pagination/pagination-button.directive.ts new file mode 100644 index 00000000..7e80ad54 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination-button.directive.ts @@ -0,0 +1,44 @@ +import type { PaginationButtonClass, PaginationButtonTheme } from './pagination-button.theme'; +import { PaginationButtonThemeService } from './pagination-button.theme.service'; + +import type { DeepPartial } from 'flowbite-angular'; +import { BaseComponent, booleanToFlowbiteBoolean } from 'flowbite-angular'; + +import { + Directive, + inject, + InjectionToken, + input, + makeEnvironmentProviders, + model, +} from '@angular/core'; + +export const FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE = new InjectionToken< + DeepPartial +>('FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE'); + +export const paginationButtonDefaultValueProvider = makeEnvironmentProviders([ + { + provide: FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE, + useValue: {}, + }, +]); + +@Directive({ + standalone: true, + selector: 'button[flowbitePaginationButton]', +}) +export class PaginationButtonDirective extends BaseComponent { + public readonly themeService = inject(PaginationButtonThemeService); + + public readonly active = input(false); + + public customStyle = model(inject(FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE)); + + public override fetchClass(): PaginationButtonClass { + return this.themeService.getClasses({ + active: booleanToFlowbiteBoolean(this.active()), + customStyle: this.customStyle(), + }); + } +} diff --git a/libs/flowbite-angular/pagination/pagination-button.theme.service.ts b/libs/flowbite-angular/pagination/pagination-button.theme.service.ts new file mode 100644 index 00000000..974165e6 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination-button.theme.service.ts @@ -0,0 +1,42 @@ +import type { + PaginationButtonClass, + PaginationButtonProperties, + PaginationButtonTheme, +} from './pagination-button.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 `PaginationButtonTheme` value + * + * @example + * ``` + * var theme = inject(FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN) + * ``` + */ +export const FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN = new InjectionToken( + 'FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN' +); + +@Injectable({ + providedIn: 'root', +}) +export class PaginationButtonThemeService + implements FlowbiteThemeService +{ + public readonly baseTheme = inject(FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN); + + public getClasses(properties: PaginationButtonProperties): PaginationButtonClass { + const theme: PaginationButtonTheme = mergeTheme(this.baseTheme, properties.customStyle); + + const output: PaginationButtonClass = { + rootClass: twMerge(theme.root.base, theme.root.active[properties.active]), + }; + + return output; + } +} diff --git a/libs/flowbite-angular/pagination/pagination-button.theme.ts b/libs/flowbite-angular/pagination/pagination-button.theme.ts new file mode 100644 index 00000000..b6453ece --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination-button.theme.ts @@ -0,0 +1,38 @@ +import type { DeepPartial, FlowbiteBoolean, FlowbiteClass } from 'flowbite-angular'; +import { createTheme } from 'flowbite-angular/utils'; + +/** + * Required properties for the class generation of `PaginationButtonDirective` + */ +export interface PaginationButtonProperties { + active: keyof FlowbiteBoolean; + customStyle: DeepPartial; +} + +/** + * Theme definition for `PaginationButtonDirective` + */ +export interface PaginationButtonTheme { + root: { + base: string; + active: FlowbiteBoolean; + }; +} + +/** + * Default theme value for `PaginationButtonDirective` + */ +export const paginationButtonTheme: PaginationButtonTheme = createTheme({ + root: { + 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: { + enabled: 'bg-red-500 dark:bg-red-700', + disabled: '', + }, + }, +}); + +/** + * Generated class definition for `PaginationButtonDirective` + */ +export type PaginationButtonClass = FlowbiteClass; diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 575f5ac2..da0640b5 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -1,6 +1,11 @@ -import type { PaginationClass, PaginationNavigation } from './pagination.theme'; +import { + FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE, + PaginationButtonDirective, +} from './pagination-button.directive'; +import type { PaginationClass, PaginationNavigation, PaginationTheme } from './pagination.theme'; import { PaginationThemeService } from './pagination.theme.service'; +import type { DeepPartial } from 'flowbite-angular'; import { BaseComponent } from 'flowbite-angular'; import { @@ -8,20 +13,35 @@ import { Component, computed, inject, + InjectionToken, input, + makeEnvironmentProviders, model, ViewEncapsulation, } from '@angular/core'; +export const FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE = new InjectionToken< + DeepPartial +>('FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE'); + +export const paginationDefaultValueProvider = makeEnvironmentProviders([ + { + provide: FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE, + useValue: {}, + }, +]); + @Component({ selector: 'flowbite-pagination', standalone: true, + imports: [PaginationButtonDirective], template: ``, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PaginationComponent extends BaseComponent { + private readonly _themeService = inject(PaginationThemeService); + + readonly currentPage = model.required(); + readonly totalItems = input.required(); + readonly tabs = input(5); + readonly pageSize = input(25); + readonly prevNext = input(true); + readonly firstLast = input(true); + readonly navigation = input('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); + } + + 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()); + } +} diff --git a/libs/flowbite-angular/pagination/pagination.theme.service.ts b/libs/flowbite-angular/pagination/pagination.theme.service.ts new file mode 100644 index 00000000..5b68dc7c --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination.theme.service.ts @@ -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( + 'FLOWBITE_PAGINATION_THEME' +); + +@Injectable({ + providedIn: 'root', +}) +export class PaginationThemeService implements FlowbiteThemeService { + 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; + } +} diff --git a/libs/flowbite-angular/pagination/pagination.theme.ts b/libs/flowbite-angular/pagination/pagination.theme.ts new file mode 100644 index 00000000..076ee4c5 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination.theme.ts @@ -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; +} + +/** + * Theme definition for `NavbarComponent` + */ +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; +} diff --git a/tsconfig.base.json b/tsconfig.base.json index e1c2e28a..e2306f3e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -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"], From 1bd73bdb08d9209d192e811cef6df0b9d20865cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Thu, 12 Dec 2024 23:03:43 +0100 Subject: [PATCH 05/28] feat(pagination): move button design to its own directive --- .../core/flowbite.theme.init.ts | 31 ++++++--- libs/flowbite-angular/pagination/index.ts | 21 +++++- .../pagination/pagination-button.directive.ts | 44 ++++++++++++ .../pagination-button.theme.service.ts | 42 ++++++++++++ .../pagination/pagination-button.theme.ts | 38 +++++++++++ .../pagination/pagination.component.ts | 68 +++++++++++++------ .../pagination/pagination.theme.service.ts | 3 +- .../pagination/pagination.theme.ts | 9 --- 8 files changed, 216 insertions(+), 40 deletions(-) create mode 100644 libs/flowbite-angular/pagination/pagination-button.directive.ts create mode 100644 libs/flowbite-angular/pagination/pagination-button.theme.service.ts create mode 100644 libs/flowbite-angular/pagination/pagination-button.theme.ts diff --git a/libs/flowbite-angular/core/flowbite.theme.init.ts b/libs/flowbite-angular/core/flowbite.theme.init.ts index ea38a3f9..c8f6cae4 100644 --- a/libs/flowbite-angular/core/flowbite.theme.init.ts +++ b/libs/flowbite-angular/core/flowbite.theme.init.ts @@ -119,7 +119,12 @@ import { NavbarToggleThemeService, } from 'flowbite-angular/navbar'; import { + FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN, FLOWBITE_PAGINATION_THEME_TOKEN, + paginationButtonDefaultValueProvider, + paginationButtonTheme, + PaginationButtonThemeService, + paginationDefaultValueProvider, paginationTheme, PaginationThemeService, } from 'flowbite-angular/pagination'; @@ -248,10 +253,6 @@ export function initFlowbite(): EnvironmentProviders { provide: NavbarBrandThemeService, useClass: NavbarBrandThemeService, }, - { - provide: PaginationThemeService, - useClass: PaginationThemeService, - }, { provide: NavbarContentThemeService, useClass: NavbarContentThemeService, @@ -272,6 +273,14 @@ export function initFlowbite(): EnvironmentProviders { provide: NavbarThemeService, useClass: NavbarThemeService, }, + { + provide: PaginationThemeService, + useClass: PaginationThemeService, + }, + { + provide: PaginationButtonThemeService, + useClass: PaginationButtonThemeService, + }, { provide: ScrollTopThemeService, useClass: ScrollTopThemeService, @@ -384,10 +393,6 @@ export function initFlowbite(): EnvironmentProviders { provide: FLOWBITE_NAVBAR_CONTENT_THEME_TOKEN, useValue: navbarContentTheme, }, - { - provide: FLOWBITE_PAGINATION_THEME_TOKEN, - useValue: paginationTheme, - }, { provide: FLOWBITE_NAVBAR_ITEM_THEME_TOKEN, useValue: navbarItemTheme, @@ -404,6 +409,14 @@ export function initFlowbite(): EnvironmentProviders { provide: FLOWBITE_NAVBAR_THEME_TOKEN, useValue: navbarTheme, }, + { + provide: FLOWBITE_PAGINATION_THEME_TOKEN, + useValue: paginationTheme, + }, + { + provide: FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN, + useValue: paginationButtonTheme, + }, { provide: FLOWBITE_SCROLL_TOP_THEME_TOKEN, useValue: scrollTopTheme, @@ -460,6 +473,8 @@ export function initFlowbite(): EnvironmentProviders { navbarIconButtonDefaultValueProvider, navbarContentDefaultValueProvider, navbarBrandDefaultThemeProvider, + paginationDefaultValueProvider, + paginationButtonDefaultValueProvider, scrollTopDefaultValueProvider, sidebarDefaultValueProvider, sidebarToggleDefaultValueProvider, diff --git a/libs/flowbite-angular/pagination/index.ts b/libs/flowbite-angular/pagination/index.ts index 90544123..008ffbdf 100644 --- a/libs/flowbite-angular/pagination/index.ts +++ b/libs/flowbite-angular/pagination/index.ts @@ -1,7 +1,26 @@ -export { PaginationComponent } from './pagination.component'; +export { + PaginationComponent, + paginationDefaultValueProvider, + FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE, +} 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'; + +export { + paginationButtonDefaultValueProvider, + FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE, +} from './pagination-button.directive'; +export type { + PaginationButtonProperties, + PaginationButtonClass, + PaginationButtonTheme, +} from './pagination-button.theme'; +export { paginationButtonTheme } from './pagination-button.theme'; +export { + PaginationButtonThemeService, + FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN, +} from './pagination-button.theme.service'; diff --git a/libs/flowbite-angular/pagination/pagination-button.directive.ts b/libs/flowbite-angular/pagination/pagination-button.directive.ts new file mode 100644 index 00000000..7e80ad54 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination-button.directive.ts @@ -0,0 +1,44 @@ +import type { PaginationButtonClass, PaginationButtonTheme } from './pagination-button.theme'; +import { PaginationButtonThemeService } from './pagination-button.theme.service'; + +import type { DeepPartial } from 'flowbite-angular'; +import { BaseComponent, booleanToFlowbiteBoolean } from 'flowbite-angular'; + +import { + Directive, + inject, + InjectionToken, + input, + makeEnvironmentProviders, + model, +} from '@angular/core'; + +export const FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE = new InjectionToken< + DeepPartial +>('FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE'); + +export const paginationButtonDefaultValueProvider = makeEnvironmentProviders([ + { + provide: FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE, + useValue: {}, + }, +]); + +@Directive({ + standalone: true, + selector: 'button[flowbitePaginationButton]', +}) +export class PaginationButtonDirective extends BaseComponent { + public readonly themeService = inject(PaginationButtonThemeService); + + public readonly active = input(false); + + public customStyle = model(inject(FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE)); + + public override fetchClass(): PaginationButtonClass { + return this.themeService.getClasses({ + active: booleanToFlowbiteBoolean(this.active()), + customStyle: this.customStyle(), + }); + } +} diff --git a/libs/flowbite-angular/pagination/pagination-button.theme.service.ts b/libs/flowbite-angular/pagination/pagination-button.theme.service.ts new file mode 100644 index 00000000..974165e6 --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination-button.theme.service.ts @@ -0,0 +1,42 @@ +import type { + PaginationButtonClass, + PaginationButtonProperties, + PaginationButtonTheme, +} from './pagination-button.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 `PaginationButtonTheme` value + * + * @example + * ``` + * var theme = inject(FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN) + * ``` + */ +export const FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN = new InjectionToken( + 'FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN' +); + +@Injectable({ + providedIn: 'root', +}) +export class PaginationButtonThemeService + implements FlowbiteThemeService +{ + public readonly baseTheme = inject(FLOWBITE_PAGINATION_BUTTON_THEME_TOKEN); + + public getClasses(properties: PaginationButtonProperties): PaginationButtonClass { + const theme: PaginationButtonTheme = mergeTheme(this.baseTheme, properties.customStyle); + + const output: PaginationButtonClass = { + rootClass: twMerge(theme.root.base, theme.root.active[properties.active]), + }; + + return output; + } +} diff --git a/libs/flowbite-angular/pagination/pagination-button.theme.ts b/libs/flowbite-angular/pagination/pagination-button.theme.ts new file mode 100644 index 00000000..b6453ece --- /dev/null +++ b/libs/flowbite-angular/pagination/pagination-button.theme.ts @@ -0,0 +1,38 @@ +import type { DeepPartial, FlowbiteBoolean, FlowbiteClass } from 'flowbite-angular'; +import { createTheme } from 'flowbite-angular/utils'; + +/** + * Required properties for the class generation of `PaginationButtonDirective` + */ +export interface PaginationButtonProperties { + active: keyof FlowbiteBoolean; + customStyle: DeepPartial; +} + +/** + * Theme definition for `PaginationButtonDirective` + */ +export interface PaginationButtonTheme { + root: { + base: string; + active: FlowbiteBoolean; + }; +} + +/** + * Default theme value for `PaginationButtonDirective` + */ +export const paginationButtonTheme: PaginationButtonTheme = createTheme({ + root: { + 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: { + enabled: 'bg-red-500 dark:bg-red-700', + disabled: '', + }, + }, +}); + +/** + * Generated class definition for `PaginationButtonDirective` + */ +export type PaginationButtonClass = FlowbiteClass; diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 575f5ac2..da0640b5 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -1,6 +1,11 @@ -import type { PaginationClass, PaginationNavigation } from './pagination.theme'; +import { + FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE, + PaginationButtonDirective, +} from './pagination-button.directive'; +import type { PaginationClass, PaginationNavigation, PaginationTheme } from './pagination.theme'; import { PaginationThemeService } from './pagination.theme.service'; +import type { DeepPartial } from 'flowbite-angular'; import { BaseComponent } from 'flowbite-angular'; import { @@ -8,20 +13,35 @@ import { Component, computed, inject, + InjectionToken, input, + makeEnvironmentProviders, model, ViewEncapsulation, } from '@angular/core'; +export const FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE = new InjectionToken< + DeepPartial +>('FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE'); + +export const paginationDefaultValueProvider = makeEnvironmentProviders([ + { + provide: FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE, + useValue: {}, + }, +]); + @Component({ selector: 'flowbite-pagination', standalone: true, + imports: [PaginationButtonDirective], template: ` + `, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -330,15 +304,13 @@ export class PaginationComponent extends BaseComponent { * * @default icon */ - readonly navigation = input( - inject(FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE) - ); + readonly navigation = input(inject(FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE)); /** * Value of the component's size * * @default md */ - readonly size = input(inject(FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE)); + readonly size = input(inject(FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE)); /** * Value of the next icon * @@ -357,6 +329,33 @@ export class PaginationComponent extends BaseComponent { * @default Pagination navigation */ readonly ariaLabel = input('Pagination navigation'); + /** + * Set the custom style for this pagination + */ + public customStyle = model(inject(FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE)); + /** + * Set the properties of all buttons in navigation + * + * @default { + color: 'primary', + fill: 'outline', + customStyle: { + root: { + base: { + default: + 'cursor-pointer !leading-tight !inline-flex !items-center !py-1.5 !ms-0 !place-content-center !rounded-none first:!rounded-l-lg last:!rounded-r-lg data-[active=false]:!text-inherit data-[active=false]:!border-inherit data-[active=false]:hover:!bg-gray-100 data-[active=false]:dark:hover:!bg-gray-700 data-[active=true]:!bg-opacity-75', + }, + size: { + sm: 'text-sm px-3 h-8', + md: 'text-base px-4 h-10', + }, + }, + }, + } + */ + public readonly buttonProperties = model( + inject(FLOWBITE_PAGINATION_BUTTON_PROPERTIES_DEFAULT_VALUE) + ); /** * Value of the first visible page @@ -414,15 +413,6 @@ export class PaginationComponent extends BaseComponent { return Math.min(this.tabs(), this.maxPages()); }); - /** - * Set the custom style for this pagination - */ - public customStyle = model(inject(FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE)); - /** - * Set the custom style for this pagination's button - */ - public buttonCustomStyle = model(inject(FLOWBITE_PAGINATION_BUTTON_CUSTOM_STYLE_DEFAULT_VALUE)); - //#region BaseComponent implementation public override fetchClass(): PaginationClass { return this.themeService.getClasses({ diff --git a/libs/flowbite-angular/pagination/pagination.theme.ts b/libs/flowbite-angular/pagination/pagination.theme.ts index 6eb9c3d3..03a0e799 100644 --- a/libs/flowbite-angular/pagination/pagination.theme.ts +++ b/libs/flowbite-angular/pagination/pagination.theme.ts @@ -49,7 +49,7 @@ export const paginationTheme: PaginationTheme = createTheme({ base: '', }, navigation: { - base: 'inline-flex -space-x-px', + base: 'group inline-flex -space-x-px border-gray-300 dark:border-gray-700', size: { sm: 'text-sm', md: 'text-base', From cb31e769ada8d209bcfa7749273f9e16232f8547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Mon, 30 Dec 2024 19:47:45 +0100 Subject: [PATCH 18/28] chore(pagination): code style updates --- libs/flowbite-angular/pagination/index.ts | 2 +- .../pagination/pagination.component.ts | 37 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/libs/flowbite-angular/pagination/index.ts b/libs/flowbite-angular/pagination/index.ts index 30b36ad2..527756c5 100644 --- a/libs/flowbite-angular/pagination/index.ts +++ b/libs/flowbite-angular/pagination/index.ts @@ -5,7 +5,7 @@ export { FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE, FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE, FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE, - FLOWBITE_PAGINATION_PAGESIZE_DEFAULT_VALUE, + FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE, FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE, FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE, FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE, diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 0b971f40..0db79355 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -8,8 +8,7 @@ import { PaginationThemeService } from './pagination.theme.service'; import type { DeepPartial } from 'flowbite-angular'; import { BaseComponent } from 'flowbite-angular'; -import type { ButtonTheme } from 'flowbite-angular/button'; -import { ButtonComponent, type ButtonProperties } from 'flowbite-angular/button'; +import { ButtonComponent, type ButtonProperties, type ButtonTheme } from 'flowbite-angular/button'; import { IconComponent, IconRegistry } from 'flowbite-angular/icon'; import { CHEVRON_DOUBLE_RIGHT_SVG_ICON, CHEVRON_RIGHT_SVG_ICON } from 'flowbite-angular/utils'; @@ -44,8 +43,8 @@ export const FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE = new InjectionToken 'FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE' ); -export const FLOWBITE_PAGINATION_PAGESIZE_DEFAULT_VALUE = new InjectionToken( - 'FLOWBITE_PAGINATION_PAGESIZE_DEFAULT_VALUE' +export const FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE = new InjectionToken( + 'FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE' ); export const FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE = new InjectionToken( @@ -86,7 +85,7 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ useValue: 5, }, { - provide: FLOWBITE_PAGINATION_PAGESIZE_DEFAULT_VALUE, + provide: FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE, useValue: 25, }, { @@ -138,7 +137,7 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ { * * @default 25 */ - readonly pageSize = input(inject(FLOWBITE_PAGINATION_PAGESIZE_DEFAULT_VALUE)); + readonly pageSize = input(inject(FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE)); /** * Whether to show or hide previous and next buttons * @@ -375,14 +374,14 @@ export class PaginationComponent extends BaseComponent { /** * Value of the maximum pages calculated from `totalItems` */ - readonly maxPages = computed(() => { + public readonly maxPages = computed(() => { return Math.max(Math.ceil(this.totalItems() / this.pageSize()), 1); }); /** * Array of the visible page tabs */ - readonly visiblePages = computed(() => { + public readonly visiblePages = computed(() => { const pages: number[] = []; for ( @@ -402,14 +401,14 @@ export class PaginationComponent extends BaseComponent { * If the given `currentPage` is bigger than the `maxPages`, * the last page will be the active one */ - readonly visibleCurrentPage = computed(() => { + public readonly visibleCurrentPage = computed(() => { return Math.min(this.currentPage(), this.maxPages()); }); /** * Value of how many page tabs to display */ - readonly visiblePagesCount = computed(() => { + public readonly visiblePagesCount = computed(() => { return Math.min(this.tabs(), this.maxPages()); }); @@ -440,7 +439,7 @@ export class PaginationComponent extends BaseComponent { * Sets the value of the `currentPage` * @param page number of the active page */ - changePage(page: number) { + public changePage(page: number) { if (this.visibleCurrentPage() === page) return; this.currentPage.set(page); } @@ -448,7 +447,7 @@ export class PaginationComponent extends BaseComponent { /** * Decreases the value of `currentPage` if it's bigger than 1 */ - previousPage() { + public goToPreviousPage() { if (this.visibleCurrentPage() === 1) return; this.currentPage.update((value) => value - 1); } @@ -456,7 +455,7 @@ export class PaginationComponent extends BaseComponent { /** * Increases the value of `currentPage` if it's smaller than `maxPages` */ - nextPage() { + public goToNextPage() { if (this.visibleCurrentPage() === this.maxPages()) return; this.currentPage.update((value) => value + 1); } @@ -464,7 +463,7 @@ export class PaginationComponent extends BaseComponent { /** * Sets the value of `currentPage` to 1 */ - firstPage() { + public goToFirstPage() { if (this.currentPage() === 1) return; this.currentPage.set(1); } @@ -472,7 +471,7 @@ export class PaginationComponent extends BaseComponent { /** * Sets the value of `currentPage` equal to `maxPages` */ - lastPage() { + public goToLastPage() { if (this.currentPage() === this.maxPages()) return; this.currentPage.set(this.maxPages()); } From 338e5f37ef38b376cc447e8e9d6f6acd10575860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Mon, 30 Dec 2024 19:53:36 +0100 Subject: [PATCH 19/28] chore(pagination): use `model` instead of `input` --- .../pagination/pagination.component.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 0db79355..cc359fa8 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -20,7 +20,6 @@ import { computed, inject, InjectionToken, - input, makeEnvironmentProviders, model, ViewEncapsulation, @@ -273,61 +272,61 @@ export class PaginationComponent extends BaseComponent { * * @required */ - readonly totalItems = input.required(); + readonly totalItems = model.required(); /** * Value of how many tabs are displayed * * @default 5 */ - readonly tabs = input(inject(FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE)); + readonly tabs = model(inject(FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE)); /** * Value of how many items are in a tab * * @default 25 */ - readonly pageSize = input(inject(FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE)); + readonly pageSize = model(inject(FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE)); /** * Whether to show or hide previous and next buttons * * @default true */ - readonly prevNext = input(inject(FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE)); + readonly prevNext = model(inject(FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE)); /** * Whether to show or hide first and last buttons * * @default true */ - readonly firstLast = input(inject(FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE)); + readonly firstLast = model(inject(FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE)); /** * Value of the navigation button's type * * @default icon */ - readonly navigation = input(inject(FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE)); + readonly navigation = model(inject(FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE)); /** * Value of the component's size * * @default md */ - readonly size = input(inject(FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE)); + readonly size = model(inject(FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE)); /** * Value of the next icon * * @default undefined */ - readonly nextIcon = input(inject(FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE)); + readonly nextIcon = model(inject(FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE)); /** * Value of the last icon * * @default undefined */ - readonly lastIcon = input(inject(FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE)); + readonly lastIcon = model(inject(FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE)); /** * Value of the aria-label * * @default Pagination navigation */ - readonly ariaLabel = input('Pagination navigation'); + readonly ariaLabel = model('Pagination navigation'); /** * Set the custom style for this pagination */ From 970b79d504b596172c1b49c93ee0bebca176d036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Mon, 30 Dec 2024 20:14:23 +0100 Subject: [PATCH 20/28] refactor(pagination): remove `nav` element, use root and rootClass --- .../pagination/_custom.component.html | 2 +- .../pagination/pagination.component.ts | 216 +++++++++--------- .../pagination/pagination.theme.service.ts | 5 +- .../pagination/pagination.theme.ts | 7 - 4 files changed, 109 insertions(+), 121 deletions(-) diff --git a/apps/docs/docs/components/pagination/_custom.component.html b/apps/docs/docs/components/pagination/_custom.component.html index c072c096..9e5be744 100644 --- a/apps/docs/docs/components/pagination/_custom.component.html +++ b/apps/docs/docs/components/pagination/_custom.component.html @@ -5,5 +5,5 @@ [firstLast]="false" [ariaLabel]="'Custom pagination'" [customStyle]="{ - navigation: { base: 'flex gap-2' }, + root: { base: 'flex gap-2' }, }" /> diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index cc359fa8..214e8d8b 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -129,120 +129,116 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ standalone: true, imports: [IconComponent, NgTemplateOutlet, ButtonComponent], template: ` - + } + @if (['text', 'both'].includes(navigation())) { + Last + } + + } `, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/libs/flowbite-angular/pagination/pagination.theme.service.ts b/libs/flowbite-angular/pagination/pagination.theme.service.ts index 43e174ab..4c4db798 100644 --- a/libs/flowbite-angular/pagination/pagination.theme.service.ts +++ b/libs/flowbite-angular/pagination/pagination.theme.service.ts @@ -15,7 +15,7 @@ import { twMerge } from 'tailwind-merge'; * ``` */ export const FLOWBITE_PAGINATION_THEME_TOKEN = new InjectionToken( - 'FLOWBITE_PAGINATION_THEME' + 'FLOWBITE_PAGINATION_THEME_TOKEN' ); @Injectable({ @@ -28,8 +28,7 @@ export class PaginationThemeService implements FlowbiteThemeService Date: Mon, 30 Dec 2024 20:21:43 +0100 Subject: [PATCH 21/28] chore(pagination): code style --- .../pagination/pagination.component.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 214e8d8b..6030f551 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -262,71 +262,71 @@ export class PaginationComponent extends BaseComponent { * * @required */ - readonly currentPage = model.required(); + public readonly currentPage = model.required(); /** * Value of the total items * * @required */ - readonly totalItems = model.required(); + public readonly totalItems = model.required(); /** * Value of how many tabs are displayed * * @default 5 */ - readonly tabs = model(inject(FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE)); + public readonly tabs = model(inject(FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE)); /** * Value of how many items are in a tab * * @default 25 */ - readonly pageSize = model(inject(FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE)); + public readonly pageSize = model(inject(FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE)); /** * Whether to show or hide previous and next buttons * * @default true */ - readonly prevNext = model(inject(FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE)); + public readonly prevNext = model(inject(FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE)); /** * Whether to show or hide first and last buttons * * @default true */ - readonly firstLast = model(inject(FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE)); + public readonly firstLast = model(inject(FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE)); /** * Value of the navigation button's type * * @default icon */ - readonly navigation = model(inject(FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE)); + public readonly navigation = model(inject(FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE)); /** * Value of the component's size * * @default md */ - readonly size = model(inject(FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE)); + public readonly size = model(inject(FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE)); /** * Value of the next icon * * @default undefined */ - readonly nextIcon = model(inject(FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE)); + public readonly nextIcon = model(inject(FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE)); /** * Value of the last icon * * @default undefined */ - readonly lastIcon = model(inject(FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE)); + public readonly lastIcon = model(inject(FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE)); /** * Value of the aria-label * * @default Pagination navigation */ - readonly ariaLabel = model('Pagination navigation'); + public readonly ariaLabel = model('Pagination navigation'); /** * Set the custom style for this pagination */ - public customStyle = model(inject(FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE)); + public readonly customStyle = model(inject(FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE)); /** * Set the properties of all buttons in navigation * From c983fca52193c18247f72ad537e2d33f0c56d0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Mon, 30 Dec 2024 20:45:53 +0100 Subject: [PATCH 22/28] feat(utils): add left/double left chevron icon --- libs/flowbite-angular/utils/icon.list.ts | 12 ++++++++++++ libs/flowbite-angular/utils/index.ts | 2 ++ 2 files changed, 14 insertions(+) diff --git a/libs/flowbite-angular/utils/icon.list.ts b/libs/flowbite-angular/utils/icon.list.ts index 6f9d8b9e..e883e579 100644 --- a/libs/flowbite-angular/utils/icon.list.ts +++ b/libs/flowbite-angular/utils/icon.list.ts @@ -17,12 +17,24 @@ export const CHEVRON_DOWN_SVG_ICON = ` `; +export const CHEVRON_LEFT_SVG_ICON = ` + + + +`; + export const CHEVRON_RIGHT_SVG_ICON = ` `; +export const CHEVRON_DOUBLE_LEFT_SVG_ICON = ` + + + +`; + export const CHEVRON_DOUBLE_RIGHT_SVG_ICON = ` diff --git a/libs/flowbite-angular/utils/index.ts b/libs/flowbite-angular/utils/index.ts index 0bc897b5..4bab4daa 100644 --- a/libs/flowbite-angular/utils/index.ts +++ b/libs/flowbite-angular/utils/index.ts @@ -8,7 +8,9 @@ export { themeToString } from './theme/to-string'; export { BARS_SVG_ICON, CHEVRON_DOWN_SVG_ICON, + CHEVRON_LEFT_SVG_ICON, CHEVRON_RIGHT_SVG_ICON, + CHEVRON_DOUBLE_LEFT_SVG_ICON, CHEVRON_DOUBLE_RIGHT_SVG_ICON, CHEVRON_UP_SVG_ICON, CLOSE_SVG_ICON, From 434bfa46052dc91e9a27244c48345a2457a24cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Mon, 30 Dec 2024 21:03:08 +0100 Subject: [PATCH 23/28] feat(pagination): use left sided icons instead of `rotate-180` --- libs/flowbite-angular/pagination/index.ts | 2 + .../pagination/pagination.component.ts | 67 +++++++++++++++---- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/libs/flowbite-angular/pagination/index.ts b/libs/flowbite-angular/pagination/index.ts index 527756c5..8b0afc95 100644 --- a/libs/flowbite-angular/pagination/index.ts +++ b/libs/flowbite-angular/pagination/index.ts @@ -2,7 +2,9 @@ export { PaginationComponent, paginationDefaultValueProvider, FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE, + FLOWBITE_PAGINATION_PREVIOUS_ICON_DEFAULT_VALUE, FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE, + FLOWBITE_PAGINATION_FIRST_ICON_DEFAULT_VALUE, FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE, FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE, FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE, diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 6030f551..bc9a8ffb 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -10,7 +10,12 @@ import type { DeepPartial } from 'flowbite-angular'; import { BaseComponent } from 'flowbite-angular'; import { ButtonComponent, type ButtonProperties, type ButtonTheme } from 'flowbite-angular/button'; import { IconComponent, IconRegistry } from 'flowbite-angular/icon'; -import { CHEVRON_DOUBLE_RIGHT_SVG_ICON, CHEVRON_RIGHT_SVG_ICON } from 'flowbite-angular/utils'; +import { + CHEVRON_DOUBLE_LEFT_SVG_ICON, + CHEVRON_DOUBLE_RIGHT_SVG_ICON, + CHEVRON_LEFT_SVG_ICON, + CHEVRON_RIGHT_SVG_ICON, +} from 'flowbite-angular/utils'; import { NgTemplateOutlet } from '@angular/common'; import type { TemplateRef } from '@angular/core'; @@ -30,10 +35,18 @@ export const FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE = new InjectionToken DeepPartial >('FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE'); +export const FLOWBITE_PAGINATION_PREVIOUS_ICON_DEFAULT_VALUE = new InjectionToken< + TemplateRef | undefined +>('FLOWBITE_PAGINATION_PREVIOUS_ICON_DEFAULT_VALUE'); + export const FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE = new InjectionToken< TemplateRef | undefined >('FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE'); +export const FLOWBITE_PAGINATION_FIRST_ICON_DEFAULT_VALUE = new InjectionToken< + TemplateRef | undefined +>('FLOWBITE_PAGINATION_FIRST_ICON_DEFAULT_VALUE'); + export const FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE = new InjectionToken< TemplateRef | undefined >('FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE'); @@ -71,10 +84,18 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ provide: FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE, useValue: {}, }, + { + provide: FLOWBITE_PAGINATION_PREVIOUS_ICON_DEFAULT_VALUE, + useValue: undefined, + }, { provide: FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE, useValue: undefined, }, + { + provide: FLOWBITE_PAGINATION_FIRST_ICON_DEFAULT_VALUE, + useValue: undefined, + }, { provide: FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE, useValue: undefined, @@ -139,13 +160,12 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ [size]="size()" [customStyle]="buttonProperties().customStyle!"> @if (['icon', 'both'].includes(navigation())) { - @if (lastIcon()) { - + @if (firstIcon()) { + } @else { + svgIcon="flowbite-angular:chevron-double-left" + [class]="contentClasses().iconClass" /> } } @if (['text', 'both'].includes(navigation())) { @@ -164,13 +184,12 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ [size]="size()" [customStyle]="buttonProperties().customStyle!"> @if (['icon', 'both'].includes(navigation())) { - @if (lastIcon()) { - + @if (previousIcon()) { + } @else { + svgIcon="flowbite-angular:chevron-left" + [class]="contentClasses().iconClass" /> } } @if (['text', 'both'].includes(navigation())) { @@ -230,7 +249,7 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ } @else { } } @@ -305,12 +324,24 @@ export class PaginationComponent extends BaseComponent { * @default md */ public readonly size = model(inject(FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE)); + /** + * Value of the previous icon + * + * @default undefined + */ + public readonly previousIcon = model(inject(FLOWBITE_PAGINATION_PREVIOUS_ICON_DEFAULT_VALUE)); /** * Value of the next icon * * @default undefined */ public readonly nextIcon = model(inject(FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE)); + /** + * Value of the first icon + * + * @default undefined + */ + public readonly firstIcon = model(inject(FLOWBITE_PAGINATION_FIRST_ICON_DEFAULT_VALUE)); /** * Value of the last icon * @@ -416,6 +447,18 @@ export class PaginationComponent extends BaseComponent { } public override init(): void { + this.iconRegistry.addRawSvgIconInNamepsace( + 'flowbite-angular', + 'chevron-left', + this.domSanitizer.bypassSecurityTrustHtml(CHEVRON_LEFT_SVG_ICON) + ); + + this.iconRegistry.addRawSvgIconInNamepsace( + 'flowbite-angular', + 'chevron-double-left', + this.domSanitizer.bypassSecurityTrustHtml(CHEVRON_DOUBLE_LEFT_SVG_ICON) + ); + this.iconRegistry.addRawSvgIconInNamepsace( 'flowbite-angular', 'chevron-right', From 0ec0dfa96a8df1b1d13a31c285f27d07c4cbc8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Mon, 30 Dec 2024 21:47:50 +0100 Subject: [PATCH 24/28] fix(pagination): place label before icon in next/last button --- .../pagination/pagination.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index bc9a8ffb..2e62a5b9 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -220,6 +220,9 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ [fill]="buttonProperties().fill!" [size]="size()" [customStyle]="buttonProperties().customStyle!"> + @if (['text', 'both'].includes(navigation())) { + Next + } @if (['icon', 'both'].includes(navigation())) { @if (lastIcon()) { @@ -229,9 +232,6 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ [class]="contentClasses().iconClass" /> } } - @if (['text', 'both'].includes(navigation())) { - Next - } } @@ -244,6 +244,9 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ [fill]="buttonProperties().fill!" [size]="size()" [customStyle]="buttonProperties().customStyle!"> + @if (['text', 'both'].includes(navigation())) { + Last + } @if (['icon', 'both'].includes(navigation())) { @if (lastIcon()) { @@ -253,9 +256,6 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ [class]="contentClasses().iconClass" /> } } - @if (['text', 'both'].includes(navigation())) { - Last - } } `, From 87b1887040ac4a246ef4d8d3028ec4e35b24e0e9 Mon Sep 17 00:00:00 2001 From: MGREMY Date: Tue, 31 Dec 2024 00:01:34 +0100 Subject: [PATCH 25/28] refactor(pagination): update post update post comment --- .../pagination/_both.component.html | 3 +- .../pagination/_default.component.html | 15 +--- .../pagination/_default.component.ts | 57 +------------ .../pagination/_text.component.html | 3 +- libs/flowbite-angular/pagination/index.ts | 8 +- .../pagination/pagination.component.ts | 84 ++++++++++--------- .../pagination/pagination.theme.ts | 4 +- 7 files changed, 57 insertions(+), 117 deletions(-) diff --git a/apps/docs/docs/components/pagination/_both.component.html b/apps/docs/docs/components/pagination/_both.component.html index bcb9758d..de67c8ca 100644 --- a/apps/docs/docs/components/pagination/_both.component.html +++ b/apps/docs/docs/components/pagination/_both.component.html @@ -1,4 +1,3 @@ + [navigationMode]="'both'" /> diff --git a/apps/docs/docs/components/pagination/_default.component.html b/apps/docs/docs/components/pagination/_default.component.html index 8e8e55b6..1edfc8d3 100644 --- a/apps/docs/docs/components/pagination/_default.component.html +++ b/apps/docs/docs/components/pagination/_default.component.html @@ -1,14 +1 @@ - - @for (item of testDatas().items; track $index) { - - - - - } -
{{ item.id }}{{ item.name }}
- - + diff --git a/apps/docs/docs/components/pagination/_default.component.ts b/apps/docs/docs/components/pagination/_default.component.ts index 91a1851f..3658c97a 100644 --- a/apps/docs/docs/components/pagination/_default.component.ts +++ b/apps/docs/docs/components/pagination/_default.component.ts @@ -1,63 +1,10 @@ import { PaginationComponent } from 'flowbite-angular/pagination'; -import { Component, signal } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ - // ng-doc-ignore-line selector: 'flowbite-demo-pagination-default', imports: [PaginationComponent], - // ng-doc-ignore-line templateUrl: './_default.component.html', }) -export class FlowbiteDefaultComponent { - private readonly _testDatas: IdNameDto[] = []; - - readonly currentPage = signal(1); - readonly pageSize = signal(10); - readonly testDatas = signal(PagedResult.empty()); - - constructor() { - // fetch test datas - for (let i = 1; i <= 105; i++) { - this._testDatas.push({ id: i, name: 'John Doe' }); - } - - // simulate API call - this.pageChangeHandler(1); - } - - pageChangeHandler(page: number) { - this.currentPage.set(page); - - // Call your API here - // The following lines of code simulates a backend query - const _items: IdNameDto[] = []; - const _start = (this.currentPage() - 1) * this.pageSize(); - const _end = Math.min(this._testDatas.length, _start + this.pageSize()); - - for (let i = _start; i < _end; i++) { - _items.push({ id: i + 1, name: 'John Doe' }); - } - - this.testDatas.set({ - items: _items, - totalItems: this._testDatas.length, - }); - } -} - -export interface IdNameDto { - id: number; - name: string; -} - -export interface PagedResult { - items: T[]; - totalItems: number; -} - -export const PagedResult = { - empty(): PagedResult { - return { items: [], totalItems: 0 }; - }, -}; +export class FlowbiteDefaultComponent {} diff --git a/apps/docs/docs/components/pagination/_text.component.html b/apps/docs/docs/components/pagination/_text.component.html index 55d98d6d..1bccc97d 100644 --- a/apps/docs/docs/components/pagination/_text.component.html +++ b/apps/docs/docs/components/pagination/_text.component.html @@ -1,4 +1,3 @@ + [navigationMode]="'text'" /> diff --git a/libs/flowbite-angular/pagination/index.ts b/libs/flowbite-angular/pagination/index.ts index 8b0afc95..c4d931d4 100644 --- a/libs/flowbite-angular/pagination/index.ts +++ b/libs/flowbite-angular/pagination/index.ts @@ -1,6 +1,8 @@ export { PaginationComponent, paginationDefaultValueProvider, + FLOWBITE_PAGINATION_CURRENT_PAGE_DEFAULT_VALUE, + FLOWBITE_PAGINATION_BUTTON_PROPERTIES_DEFAULT_VALUE, FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE, FLOWBITE_PAGINATION_PREVIOUS_ICON_DEFAULT_VALUE, FLOWBITE_PAGINATION_NEXT_ICON_DEFAULT_VALUE, @@ -8,9 +10,9 @@ export { FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE, FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE, FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE, - FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE, - FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE, - FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE, + FLOWBITE_PAGINATION_HAS_FIRST_LAST_DEFAULT_VALUE, + FLOWBITE_PAGINATION_HAS_PREV_NEXT_DEFAULT_VALUE, + FLOWBITE_PAGINATION_NAVIGATION_MODE_DEFAULT_VALUE, FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE, } from './pagination.component'; export type { PaginationProperties, PaginationClass, PaginationTheme } from './pagination.theme'; diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 2e62a5b9..40774c27 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -31,6 +31,10 @@ import { } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; +export const FLOWBITE_PAGINATION_CURRENT_PAGE_DEFAULT_VALUE = new InjectionToken( + 'FLOWBITE_PAGINATION_CURRENT_PAGE_DEFAULT_VALUE' +); + export const FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE = new InjectionToken< DeepPartial >('FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE'); @@ -59,17 +63,17 @@ export const FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE = new InjectionToken( - 'FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE' +export const FLOWBITE_PAGINATION_HAS_FIRST_LAST_DEFAULT_VALUE = new InjectionToken( + 'FLOWBITE_PAGINATION_HAS_FIRST_LAST_DEFAULT_VALUE' ); -export const FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE = new InjectionToken( - 'FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE' +export const FLOWBITE_PAGINATION_HAS_PREV_NEXT_DEFAULT_VALUE = new InjectionToken( + 'FLOWBITE_PAGINATION_HAS_PREV_NEXT_DEFAULT_VALUE' ); -export const FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE = new InjectionToken< +export const FLOWBITE_PAGINATION_NAVIGATION_MODE_DEFAULT_VALUE = new InjectionToken< keyof PaginationNavigation ->('FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE'); +>('FLOWBITE_PAGINATION_NAVIGATION_MODE_DEFAULT_VALUE'); export const FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE = new InjectionToken( 'FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE' @@ -80,6 +84,10 @@ export const FLOWBITE_PAGINATION_BUTTON_PROPERTIES_DEFAULT_VALUE = new Injection >('FLOWBITE_PAGINATION_BUTTON_PROPERTIES_DEFAULT_VALUE'); export const paginationDefaultValueProvider = makeEnvironmentProviders([ + { + provide: FLOWBITE_PAGINATION_CURRENT_PAGE_DEFAULT_VALUE, + useValue: 1, + }, { provide: FLOWBITE_PAGINATION_CUSTOM_STYLE_DEFAULT_VALUE, useValue: {}, @@ -109,15 +117,15 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ useValue: 25, }, { - provide: FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE, + provide: FLOWBITE_PAGINATION_HAS_FIRST_LAST_DEFAULT_VALUE, useValue: true, }, { - provide: FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE, + provide: FLOWBITE_PAGINATION_HAS_PREV_NEXT_DEFAULT_VALUE, useValue: true, }, { - provide: FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE, + provide: FLOWBITE_PAGINATION_NAVIGATION_MODE_DEFAULT_VALUE, useValue: 'icon', }, { @@ -150,7 +158,7 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ standalone: true, imports: [IconComponent, NgTemplateOutlet, ButtonComponent], template: ` - @if (firstLast()) { + @if (hasFirstLast()) { - @if (['icon', 'both'].includes(navigation())) { + @if (['icon', 'both'].includes(navigationMode())) { @if (firstIcon()) { } @else { @@ -168,13 +176,13 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ [class]="contentClasses().iconClass" /> } } - @if (['text', 'both'].includes(navigation())) { + @if (['text', 'both'].includes(navigationMode())) { First } } - @if (prevNext()) { + @if (hasPrevNext()) { - @if (['icon', 'both'].includes(navigation())) { + @if (['icon', 'both'].includes(navigationMode())) { @if (previousIcon()) { } @else { @@ -192,7 +200,7 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ [class]="contentClasses().iconClass" /> } } - @if (['text', 'both'].includes(navigation())) { + @if (['text', 'both'].includes(navigationMode())) { Previous } @@ -202,7 +210,7 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ } - @if (prevNext()) { + @if (hasPrevNext()) { - @if (['text', 'both'].includes(navigation())) { + @if (['text', 'both'].includes(navigationMode())) { Next } - @if (['icon', 'both'].includes(navigation())) { + @if (['icon', 'both'].includes(navigationMode())) { @if (lastIcon()) { } @else { @@ -235,7 +243,7 @@ export const paginationDefaultValueProvider = makeEnvironmentProviders([ } - @if (firstLast()) { + @if (hasFirstLast()) { - @if (['text', 'both'].includes(navigation())) { + @if (['text', 'both'].includes(navigationMode())) { Last } - @if (['icon', 'both'].includes(navigation())) { + @if (['icon', 'both'].includes(navigationMode())) { @if (lastIcon()) { } @else { @@ -277,17 +285,17 @@ export class PaginationComponent extends BaseComponent { public readonly domSanitizer = inject(DomSanitizer); /** - * Value of the current page + * Value of the total items * * @required */ - public readonly currentPage = model.required(); + public readonly totalItems = model.required(); /** - * Value of the total items + * Value of the current page * - * @required + * @default 1 */ - public readonly totalItems = model.required(); + public readonly currentPage = model(inject(FLOWBITE_PAGINATION_CURRENT_PAGE_DEFAULT_VALUE)); /** * Value of how many tabs are displayed * @@ -305,19 +313,19 @@ export class PaginationComponent extends BaseComponent { * * @default true */ - public readonly prevNext = model(inject(FLOWBITE_PAGINATION_PREVNEXT_DEFAULT_VALUE)); + public readonly hasPrevNext = model(inject(FLOWBITE_PAGINATION_HAS_PREV_NEXT_DEFAULT_VALUE)); /** * Whether to show or hide first and last buttons * * @default true */ - public readonly firstLast = model(inject(FLOWBITE_PAGINATION_FIRSTLAST_DEFAULT_VALUE)); + public readonly hasFirstLast = model(inject(FLOWBITE_PAGINATION_HAS_FIRST_LAST_DEFAULT_VALUE)); /** * Value of the navigation button's type * * @default icon */ - public readonly navigation = model(inject(FLOWBITE_PAGINATION_NAVIGATION_DEFAULT_VALUE)); + public readonly navigationMode = model(inject(FLOWBITE_PAGINATION_NAVIGATION_MODE_DEFAULT_VALUE)); /** * Value of the component's size * @@ -474,27 +482,27 @@ export class PaginationComponent extends BaseComponent { //#endregion /** - * Sets the value of the `currentPage` + * Sets the value of the `currentPage` if it's between 1 and `maxPages` * @param page number of the active page */ - public changePage(page: number) { - if (this.visibleCurrentPage() === page) return; + public goToPage(page: number) { + if (page < 1 || page > this.maxPages()) return; this.currentPage.set(page); } /** - * Decreases the value of `currentPage` if it's bigger than 1 + * Decreases the value of `currentPage` if it's more than 1 */ public goToPreviousPage() { - if (this.visibleCurrentPage() === 1) return; + if (this.visibleCurrentPage() <= 1) return; this.currentPage.update((value) => value - 1); } /** - * Increases the value of `currentPage` if it's smaller than `maxPages` + * Increases the value of `currentPage` if it's less than `maxPages` */ public goToNextPage() { - if (this.visibleCurrentPage() === this.maxPages()) return; + if (this.visibleCurrentPage() >= this.maxPages()) return; this.currentPage.update((value) => value + 1); } @@ -502,7 +510,6 @@ export class PaginationComponent extends BaseComponent { * Sets the value of `currentPage` to 1 */ public goToFirstPage() { - if (this.currentPage() === 1) return; this.currentPage.set(1); } @@ -510,7 +517,6 @@ export class PaginationComponent extends BaseComponent { * Sets the value of `currentPage` equal to `maxPages` */ public goToLastPage() { - if (this.currentPage() === this.maxPages()) return; this.currentPage.set(this.maxPages()); } } diff --git a/libs/flowbite-angular/pagination/pagination.theme.ts b/libs/flowbite-angular/pagination/pagination.theme.ts index 19a5d0bf..40749cb9 100644 --- a/libs/flowbite-angular/pagination/pagination.theme.ts +++ b/libs/flowbite-angular/pagination/pagination.theme.ts @@ -21,12 +21,12 @@ export interface PaginationSizes extends Pick { * Required properties for class generation of `PaginationComponent` */ export interface PaginationProperties { - customStyle: DeepPartial; size: keyof PaginationSizes; + customStyle: DeepPartial; } /** - * Theme definition for `NavbarComponent` + * Theme definition for `PaginationComponent` */ export interface PaginationTheme { root: { From 021058b665d74495dd3feb71beac19091cea31d8 Mon Sep 17 00:00:00 2001 From: MGREMY Date: Tue, 31 Dec 2024 00:14:59 +0100 Subject: [PATCH 26/28] refactor(pagination): fix doc attribute --- apps/docs/docs/components/pagination/_custom.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/components/pagination/_custom.component.html b/apps/docs/docs/components/pagination/_custom.component.html index 9e5be744..19f614d8 100644 --- a/apps/docs/docs/components/pagination/_custom.component.html +++ b/apps/docs/docs/components/pagination/_custom.component.html @@ -2,7 +2,7 @@ [currentPage]="20" [totalItems]="100000" [pageSize]="50" - [firstLast]="false" + [hasFirstLast]="false" [ariaLabel]="'Custom pagination'" [customStyle]="{ root: { base: 'flex gap-2' }, From bd238c4e6917396570141480e259888d26c62c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Tue, 31 Dec 2024 13:14:35 +0100 Subject: [PATCH 27/28] feat(pagination): possibility to use `totalPages` or `totalItems` --- .../pagination/pagination.component.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 40774c27..7070c035 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -286,10 +286,12 @@ export class PaginationComponent extends BaseComponent { /** * Value of the total items - * - * @required */ - public readonly totalItems = model.required(); + public readonly totalItems = model(); + /** + * Value of the total pages + */ + public readonly totalPages = model(); /** * Value of the current page * @@ -406,10 +408,26 @@ export class PaginationComponent extends BaseComponent { }); /** - * Value of the maximum pages calculated from `totalItems` + * Value of the maximum pages. If `totalPages` is given, it will be + * equal to that; otherwise, it is calculated from `totalItems`. */ public readonly maxPages = computed(() => { - return Math.max(Math.ceil(this.totalItems() / this.pageSize()), 1); + if (this.totalPages() !== undefined) { + /** + * Note that if we return just `this.totalPages()`, the type of the computed + * will be `Signal`, even though there is a check. + * So instead of that, we return `this.totalPages()!` to ensure + * the type is always `Signal`. + */ + return this.totalPages()!; + } + + /** + * The same applies here, except there is no need to check for undefined, + * because if `totalPages` is undefined, `totalItems` must have + * a valid number value. We check it in the init function. + */ + return Math.max(Math.ceil(this.totalItems()! / this.pageSize()), 1); }); /** @@ -455,6 +473,10 @@ export class PaginationComponent extends BaseComponent { } public override init(): void { + if (this.totalPages() === undefined && this.totalItems() === undefined) { + throw new Error('Either `totalPages` or `totalItems` must have a value'); + } + this.iconRegistry.addRawSvgIconInNamepsace( 'flowbite-angular', 'chevron-left', From e8860f1ff762730e6610252cacdf58be261abef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Lov=C3=A1sz?= Date: Tue, 31 Dec 2024 13:21:57 +0100 Subject: [PATCH 28/28] refactor(pagination): use `Array.from` instead of for loop --- .../pagination/pagination.component.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/libs/flowbite-angular/pagination/pagination.component.ts b/libs/flowbite-angular/pagination/pagination.component.ts index 7070c035..4f46bed4 100644 --- a/libs/flowbite-angular/pagination/pagination.component.ts +++ b/libs/flowbite-angular/pagination/pagination.component.ts @@ -434,17 +434,7 @@ export class PaginationComponent extends BaseComponent { * Array of the visible page tabs */ public readonly visiblePages = computed(() => { - const pages: number[] = []; - - for ( - let i = this.firstPageToShow(); - i < this.firstPageToShow() + this.visiblePagesCount(); - i++ - ) { - pages.push(i); - } - - return pages; + return Array.from({ length: this.visiblePagesCount() }, (_, i) => this.firstPageToShow() + i); }); /**