diff --git a/apps/docs/docs/components/pagination/_both.component.html b/apps/docs/docs/components/pagination/_both.component.html
new file mode 100644
index 00000000..de67c8ca
--- /dev/null
+++ b/apps/docs/docs/components/pagination/_both.component.html
@@ -0,0 +1,3 @@
+
diff --git a/apps/docs/docs/components/pagination/_both.component.ts b/apps/docs/docs/components/pagination/_both.component.ts
new file mode 100644
index 00000000..b8598de6
--- /dev/null
+++ b/apps/docs/docs/components/pagination/_both.component.ts
@@ -0,0 +1,10 @@
+import { PaginationComponent } from 'flowbite-angular/pagination';
+
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'flowbite-demo-pagination-both',
+ imports: [PaginationComponent],
+ templateUrl: './_both.component.html',
+})
+export class FlowbiteBothComponent {}
diff --git a/apps/docs/docs/components/pagination/_custom.component.html b/apps/docs/docs/components/pagination/_custom.component.html
new file mode 100644
index 00000000..19f614d8
--- /dev/null
+++ b/apps/docs/docs/components/pagination/_custom.component.html
@@ -0,0 +1,9 @@
+
diff --git a/apps/docs/docs/components/pagination/_custom.component.ts b/apps/docs/docs/components/pagination/_custom.component.ts
new file mode 100644
index 00000000..7d963383
--- /dev/null
+++ b/apps/docs/docs/components/pagination/_custom.component.ts
@@ -0,0 +1,10 @@
+import { PaginationComponent } from 'flowbite-angular/pagination';
+
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'flowbite-demo-pagination-custom',
+ imports: [PaginationComponent],
+ templateUrl: './_custom.component.html',
+})
+export class FlowbiteCustomComponent {}
diff --git a/apps/docs/docs/components/pagination/_default.component.html b/apps/docs/docs/components/pagination/_default.component.html
new file mode 100644
index 00000000..1edfc8d3
--- /dev/null
+++ b/apps/docs/docs/components/pagination/_default.component.html
@@ -0,0 +1 @@
+
diff --git a/apps/docs/docs/components/pagination/_default.component.ts b/apps/docs/docs/components/pagination/_default.component.ts
new file mode 100644
index 00000000..3658c97a
--- /dev/null
+++ b/apps/docs/docs/components/pagination/_default.component.ts
@@ -0,0 +1,10 @@
+import { PaginationComponent } from 'flowbite-angular/pagination';
+
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'flowbite-demo-pagination-default',
+ imports: [PaginationComponent],
+ templateUrl: './_default.component.html',
+})
+export class FlowbiteDefaultComponent {}
diff --git a/apps/docs/docs/components/pagination/_text.component.html b/apps/docs/docs/components/pagination/_text.component.html
new file mode 100644
index 00000000..1bccc97d
--- /dev/null
+++ b/apps/docs/docs/components/pagination/_text.component.html
@@ -0,0 +1,3 @@
+
diff --git a/apps/docs/docs/components/pagination/_text.component.ts b/apps/docs/docs/components/pagination/_text.component.ts
new file mode 100644
index 00000000..399b549e
--- /dev/null
+++ b/apps/docs/docs/components/pagination/_text.component.ts
@@ -0,0 +1,10 @@
+import { PaginationComponent } from 'flowbite-angular/pagination';
+
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'flowbite-demo-pagination-text',
+ imports: [PaginationComponent],
+ templateUrl: './_text.component.html',
+})
+export class FlowbiteTextComponent {}
diff --git a/apps/docs/docs/components/pagination/index.md b/apps/docs/docs/components/pagination/index.md
new file mode 100644
index 00000000..25811e20
--- /dev/null
+++ b/apps/docs/docs/components/pagination/index.md
@@ -0,0 +1,51 @@
+---
+keyword: PaginationPage
+---
+
+## Default pagination
+
+{{ NgDocActions.demo('flowbiteDefaultComponent', {container: false}) }}
+
+```angular-html file="./_default.component.html" group="default" name="html"
+
+```
+
+```angular-ts file="./_default.component.ts" group="default" name="typescript"
+
+```
+
+## Pagination with text
+
+{{ NgDocActions.demo('flowbiteTextComponent', {container: false}) }}
+
+```angular-html file="./_text.component.html" group="text" name="html"
+
+```
+
+```angular-ts file="./_text.component.ts"#L1 group="text" name="typescript"
+
+```
+
+## Pagination with text and icon
+
+{{ NgDocActions.demo('flowbiteBothComponent', {container: false}) }}
+
+```angular-html file="./_both.component.html" group="both" name="html"
+
+```
+
+```angular-ts file="./_both.component.ts"#L1 group="both" name="typescript"
+
+```
+
+## Customized pagination
+
+{{ NgDocActions.demo('flowbiteCustomComponent', {container: false}) }}
+
+```angular-html file="./_custom.component.html" group="custom" name="html"
+
+```
+
+```angular-ts file="./_custom.component.ts"#L1 group="custom" name="typescript"
+
+```
diff --git a/apps/docs/docs/components/pagination/ng-doc.page.ts b/apps/docs/docs/components/pagination/ng-doc.page.ts
new file mode 100644
index 00000000..c5f84115
--- /dev/null
+++ b/apps/docs/docs/components/pagination/ng-doc.page.ts
@@ -0,0 +1,27 @@
+import ComponentCategory from '../ng-doc.category';
+import { FlowbiteBothComponent } from './_both.component';
+import { FlowbiteCustomComponent } from './_custom.component';
+import { FlowbiteDefaultComponent } from './_default.component';
+import { FlowbiteTextComponent } from './_text.component';
+
+import type { NgDocPage } from '@ng-doc/core';
+
+/**
+ * Use the pagination component to show a list of buttons to navigate in your tables
+ *
+ * @status:info NEW
+ */
+const pagination: NgDocPage = {
+ title: 'Pagination',
+ mdFile: './index.md',
+ category: ComponentCategory,
+ order: 10,
+ demos: {
+ flowbiteDefaultComponent: FlowbiteDefaultComponent,
+ flowbiteTextComponent: FlowbiteTextComponent,
+ flowbiteBothComponent: FlowbiteBothComponent,
+ flowbiteCustomComponent: FlowbiteCustomComponent,
+ },
+};
+
+export default pagination;
diff --git a/apps/docs/docs/getting-started/quickstart/ng-doc.page.ts b/apps/docs/docs/getting-started/quickstart/ng-doc.page.ts
index 600ae44c..0d5fc9a6 100644
--- a/apps/docs/docs/getting-started/quickstart/ng-doc.page.ts
+++ b/apps/docs/docs/getting-started/quickstart/ng-doc.page.ts
@@ -5,7 +5,7 @@ import type { NgDocPage } from '@ng-doc/core';
/**
* Get started with flowbite-angular by including it into your project using NPM
*
- * @status:alert UPDATES
+ * @status:success UPDATES
*/
const Quickstart: NgDocPage = {
title: 'Quickstart',
diff --git a/apps/docs/docs/getting-started/versioning/index.md b/apps/docs/docs/getting-started/versioning/index.md
index 37c571c0..0d9ceb35 100644
--- a/apps/docs/docs/getting-started/versioning/index.md
+++ b/apps/docs/docs/getting-started/versioning/index.md
@@ -6,6 +6,8 @@ keyword: VersioningPage
| Flowbite-angular version | Angular version | TailwindCSS version |
| ------------------------ | --------------- | ------------------- |
+| 1.4.0 | >=18.0.0 | ^3.0.0 |
+| 1.3.0 | >=18.0.0 | ^3.0.0 |
| 1.2.0 | >=18.0.0 | ^3.0.0 |
| 1.1.1 | 18.0.0 | ^3.0.24 |
| 1.0.0 | 18.0.0 | ^3.0.24 |
diff --git a/apps/docs/docs/getting-started/versioning/ng-doc.page.ts b/apps/docs/docs/getting-started/versioning/ng-doc.page.ts
index 161c026c..5bbf3680 100644
--- a/apps/docs/docs/getting-started/versioning/ng-doc.page.ts
+++ b/apps/docs/docs/getting-started/versioning/ng-doc.page.ts
@@ -6,7 +6,7 @@ import type { NgDocPage } from '@ng-doc/core';
* Here is a reference table that matches versions of flowbite-angular with its Angular version. It
* also shows the TailwindCSS version used.
*
- * @status:alert NEW
+ * @status:info NEW
*/
const Versioning: NgDocPage = {
title: 'Versioning',
diff --git a/apps/docs/docs/ng-doc.api.ts b/apps/docs/docs/ng-doc.api.ts
index 00fd14c8..20fa44af 100644
--- a/apps/docs/docs/ng-doc.api.ts
+++ b/apps/docs/docs/ng-doc.api.ts
@@ -32,6 +32,7 @@ const api: NgDocApi = {
'libs/flowbite-angular/indicator/index.ts',
'libs/flowbite-angular/modal/index.ts',
'libs/flowbite-angular/navbar/index.ts',
+ 'libs/flowbite-angular/pagination/index.ts',
'libs/flowbite-angular/scroll-top/index.ts',
'libs/flowbite-angular/sidebar/index.ts',
'libs/flowbite-angular/theme/index.ts',
diff --git a/libs/flowbite-angular/core/flowbite.theme.init.ts b/libs/flowbite-angular/core/flowbite.theme.init.ts
index e73e8cf5..f68c5585 100644
--- a/libs/flowbite-angular/core/flowbite.theme.init.ts
+++ b/libs/flowbite-angular/core/flowbite.theme.init.ts
@@ -118,6 +118,12 @@ import {
navbarToggleTheme,
NavbarToggleThemeService,
} from 'flowbite-angular/navbar';
+import {
+ FLOWBITE_PAGINATION_THEME_TOKEN,
+ paginationDefaultValueProvider,
+ paginationTheme,
+ PaginationThemeService,
+} from 'flowbite-angular/pagination';
import {
FLOWBITE_SCROLL_TOP_THEME_TOKEN,
scrollTopDefaultValueProvider,
@@ -263,6 +269,10 @@ export function initFlowbite(): EnvironmentProviders {
provide: NavbarThemeService,
useClass: NavbarThemeService,
},
+ {
+ provide: PaginationThemeService,
+ useClass: PaginationThemeService,
+ },
{
provide: ScrollTopThemeService,
useClass: ScrollTopThemeService,
@@ -391,6 +401,10 @@ export function initFlowbite(): EnvironmentProviders {
provide: FLOWBITE_NAVBAR_THEME_TOKEN,
useValue: navbarTheme,
},
+ {
+ provide: FLOWBITE_PAGINATION_THEME_TOKEN,
+ useValue: paginationTheme,
+ },
{
provide: FLOWBITE_SCROLL_TOP_THEME_TOKEN,
useValue: scrollTopTheme,
@@ -447,6 +461,7 @@ export function initFlowbite(): EnvironmentProviders {
navbarIconButtonDefaultValueProvider,
navbarContentDefaultValueProvider,
navbarBrandDefaultThemeProvider,
+ paginationDefaultValueProvider,
scrollTopDefaultValueProvider,
sidebarDefaultValueProvider,
sidebarToggleDefaultValueProvider,
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..c4d931d4
--- /dev/null
+++ b/libs/flowbite-angular/pagination/index.ts
@@ -0,0 +1,23 @@
+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,
+ FLOWBITE_PAGINATION_FIRST_ICON_DEFAULT_VALUE,
+ FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE,
+ FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE,
+ FLOWBITE_PAGINATION_PAGE_SIZE_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';
+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..4f46bed4
--- /dev/null
+++ b/libs/flowbite-angular/pagination/pagination.component.ts
@@ -0,0 +1,534 @@
+import type {
+ PaginationClass,
+ PaginationNavigation,
+ PaginationSizes,
+ PaginationTheme,
+} from './pagination.theme';
+import { PaginationThemeService } from './pagination.theme.service';
+
+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_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';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ inject,
+ InjectionToken,
+ makeEnvironmentProviders,
+ model,
+ ViewEncapsulation,
+} 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');
+
+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');
+
+export const FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE = new InjectionToken(
+ 'FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE'
+);
+
+export const FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE = new InjectionToken(
+ 'FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE'
+);
+
+export const FLOWBITE_PAGINATION_HAS_FIRST_LAST_DEFAULT_VALUE = new InjectionToken(
+ 'FLOWBITE_PAGINATION_HAS_FIRST_LAST_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_MODE_DEFAULT_VALUE = new InjectionToken<
+ keyof PaginationNavigation
+>('FLOWBITE_PAGINATION_NAVIGATION_MODE_DEFAULT_VALUE');
+
+export const FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE = new InjectionToken(
+ 'FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE'
+);
+
+export const FLOWBITE_PAGINATION_BUTTON_PROPERTIES_DEFAULT_VALUE = new InjectionToken<
+ DeepPartial
+>('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: {},
+ },
+ {
+ 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,
+ },
+ {
+ provide: FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE,
+ useValue: 5,
+ },
+ {
+ provide: FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE,
+ useValue: 25,
+ },
+ {
+ provide: FLOWBITE_PAGINATION_HAS_FIRST_LAST_DEFAULT_VALUE,
+ useValue: true,
+ },
+ {
+ provide: FLOWBITE_PAGINATION_HAS_PREV_NEXT_DEFAULT_VALUE,
+ useValue: true,
+ },
+ {
+ provide: FLOWBITE_PAGINATION_NAVIGATION_MODE_DEFAULT_VALUE,
+ useValue: 'icon',
+ },
+ {
+ provide: FLOWBITE_PAGINATION_SIZE_DEFAULT_VALUE,
+ useValue: 'md',
+ },
+ {
+ provide: FLOWBITE_PAGINATION_BUTTON_PROPERTIES_DEFAULT_VALUE,
+ useValue: {
+ 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',
+ },
+ },
+ } as DeepPartial,
+ } as DeepPartial,
+ },
+]);
+
+@Component({
+ selector: 'flowbite-pagination',
+ standalone: true,
+ imports: [IconComponent, NgTemplateOutlet, ButtonComponent],
+ template: `
+ @if (hasFirstLast()) {
+
+ @if (['icon', 'both'].includes(navigationMode())) {
+ @if (firstIcon()) {
+
+ } @else {
+
+ }
+ }
+ @if (['text', 'both'].includes(navigationMode())) {
+ First
+ }
+
+ }
+
+ @if (hasPrevNext()) {
+
+ @if (['icon', 'both'].includes(navigationMode())) {
+ @if (previousIcon()) {
+
+ } @else {
+
+ }
+ }
+ @if (['text', 'both'].includes(navigationMode())) {
+ Previous
+ }
+
+ }
+
+ @for (page of visiblePages(); track $index) {
+
+ {{ page }}
+
+ }
+
+ @if (hasPrevNext()) {
+
+ @if (['text', 'both'].includes(navigationMode())) {
+ Next
+ }
+ @if (['icon', 'both'].includes(navigationMode())) {
+ @if (lastIcon()) {
+
+ } @else {
+
+ }
+ }
+
+ }
+
+ @if (hasFirstLast()) {
+
+ @if (['text', 'both'].includes(navigationMode())) {
+ Last
+ }
+ @if (['icon', 'both'].includes(navigationMode())) {
+ @if (lastIcon()) {
+
+ } @else {
+
+ }
+ }
+
+ }
+ `,
+ encapsulation: ViewEncapsulation.None,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class PaginationComponent extends BaseComponent {
+ /**
+ * Service injected used to generate class
+ */
+ public readonly themeService = inject(PaginationThemeService);
+ /**
+ * `IconRegistry` service
+ */
+ public readonly iconRegistry = inject(IconRegistry);
+ /**
+ * `DomSanitizer` service
+ */
+ public readonly domSanitizer = inject(DomSanitizer);
+
+ /**
+ * Value of the total items
+ */
+ public readonly totalItems = model();
+ /**
+ * Value of the total pages
+ */
+ public readonly totalPages = model();
+ /**
+ * Value of the current page
+ *
+ * @default 1
+ */
+ public readonly currentPage = model(inject(FLOWBITE_PAGINATION_CURRENT_PAGE_DEFAULT_VALUE));
+ /**
+ * Value of how many tabs are displayed
+ *
+ * @default 5
+ */
+ public readonly tabs = model(inject(FLOWBITE_PAGINATION_TABS_DEFAULT_VALUE));
+ /**
+ * Value of how many items are in a tab
+ *
+ * @default 25
+ */
+ public readonly pageSize = model(inject(FLOWBITE_PAGINATION_PAGE_SIZE_DEFAULT_VALUE));
+ /**
+ * Whether to show or hide previous and next buttons
+ *
+ * @default true
+ */
+ 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 hasFirstLast = model(inject(FLOWBITE_PAGINATION_HAS_FIRST_LAST_DEFAULT_VALUE));
+ /**
+ * Value of the navigation button's type
+ *
+ * @default icon
+ */
+ public readonly navigationMode = model(inject(FLOWBITE_PAGINATION_NAVIGATION_MODE_DEFAULT_VALUE));
+ /**
+ * Value of the component's size
+ *
+ * @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
+ *
+ * @default undefined
+ */
+ public readonly lastIcon = model(inject(FLOWBITE_PAGINATION_LAST_ICON_DEFAULT_VALUE));
+ /**
+ * Value of the aria-label
+ *
+ * @default Pagination navigation
+ */
+ public readonly ariaLabel = model('Pagination navigation');
+ /**
+ * Set the custom style for this pagination
+ */
+ public readonly 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
+ */
+ public readonly firstPageToShow = computed(() => {
+ if (this.currentPage() <= Math.floor(this.tabs() / 2)) {
+ return 1;
+ }
+
+ if (this.currentPage() > this.maxPages() - Math.ceil(this.tabs() / 2)) {
+ return this.maxPages() - this.visiblePagesCount() + 1;
+ }
+
+ return this.currentPage() - Math.floor(this.tabs() / 2);
+ });
+
+ /**
+ * 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(() => {
+ 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);
+ });
+
+ /**
+ * Array of the visible page tabs
+ */
+ public readonly visiblePages = computed(() => {
+ return Array.from({ length: this.visiblePagesCount() }, (_, i) => this.firstPageToShow() + i);
+ });
+
+ /**
+ * Real value of the current page
+ *
+ * If the given `currentPage` is bigger than the `maxPages`,
+ * the last page will be the active one
+ */
+ public readonly visibleCurrentPage = computed(() => {
+ return Math.min(this.currentPage(), this.maxPages());
+ });
+
+ /**
+ * Value of how many page tabs to display
+ */
+ public readonly visiblePagesCount = computed(() => {
+ return Math.min(this.tabs(), this.maxPages());
+ });
+
+ //#region BaseComponent implementation
+ public override fetchClass(): PaginationClass {
+ return this.themeService.getClasses({
+ customStyle: this.customStyle(),
+ size: this.size(),
+ });
+ }
+
+ 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',
+ 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',
+ this.domSanitizer.bypassSecurityTrustHtml(CHEVRON_RIGHT_SVG_ICON)
+ );
+
+ this.iconRegistry.addRawSvgIconInNamepsace(
+ 'flowbite-angular',
+ 'chevron-double-right',
+ this.domSanitizer.bypassSecurityTrustHtml(CHEVRON_DOUBLE_RIGHT_SVG_ICON)
+ );
+ }
+ //#endregion
+
+ /**
+ * Sets the value of the `currentPage` if it's between 1 and `maxPages`
+ * @param page number of the active page
+ */
+ public goToPage(page: number) {
+ if (page < 1 || page > this.maxPages()) return;
+ this.currentPage.set(page);
+ }
+
+ /**
+ * Decreases the value of `currentPage` if it's more than 1
+ */
+ public goToPreviousPage() {
+ if (this.visibleCurrentPage() <= 1) return;
+ this.currentPage.update((value) => value - 1);
+ }
+
+ /**
+ * Increases the value of `currentPage` if it's less than `maxPages`
+ */
+ public goToNextPage() {
+ if (this.visibleCurrentPage() >= this.maxPages()) return;
+ this.currentPage.update((value) => value + 1);
+ }
+
+ /**
+ * Sets the value of `currentPage` to 1
+ */
+ public goToFirstPage() {
+ this.currentPage.set(1);
+ }
+
+ /**
+ * Sets the value of `currentPage` equal to `maxPages`
+ */
+ public goToLastPage() {
+ 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..4c4db798
--- /dev/null
+++ b/libs/flowbite-angular/pagination/pagination.theme.service.ts
@@ -0,0 +1,37 @@
+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_TOKEN'
+);
+
+@Injectable({
+ providedIn: 'root',
+})
+export class PaginationThemeService implements FlowbiteThemeService {
+ public 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, theme.root.size[properties.size]),
+ iconClass: twMerge(theme.icon.size[properties.size]),
+ };
+
+ 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..40749cb9
--- /dev/null
+++ b/libs/flowbite-angular/pagination/pagination.theme.ts
@@ -0,0 +1,65 @@
+import type { DeepPartial, FlowbiteClass, FlowbiteSizes } from 'flowbite-angular';
+import { createTheme } from 'flowbite-angular/utils';
+
+/**
+ * Available navigation types for `PaginationButtonDirective`
+ */
+export interface PaginationNavigation {
+ icon: string;
+ text: string;
+ both: string;
+}
+
+/**
+ * Available sizes for `PaginationComponent`
+ */
+export interface PaginationSizes extends Pick {
+ [key: string]: string;
+}
+
+/**
+ * Required properties for class generation of `PaginationComponent`
+ */
+export interface PaginationProperties {
+ size: keyof PaginationSizes;
+ customStyle: DeepPartial;
+}
+
+/**
+ * Theme definition for `PaginationComponent`
+ */
+export interface PaginationTheme {
+ root: {
+ base: string;
+ size: PaginationSizes;
+ };
+ icon: {
+ size: PaginationSizes;
+ };
+}
+
+/**
+ * Default theme for `PaginationComponent`
+ */
+export const paginationTheme: PaginationTheme = createTheme({
+ root: {
+ base: 'group inline-flex -space-x-px border-gray-300 dark:border-gray-700',
+ size: {
+ sm: 'text-sm',
+ md: 'text-base',
+ },
+ },
+ icon: {
+ size: {
+ sm: 'w-5 h-5',
+ md: 'w-6 h-6',
+ },
+ },
+});
+
+/**
+ * Generated class definition for `PaginationComponent`
+ */
+export interface PaginationClass extends FlowbiteClass {
+ iconClass: string;
+}
diff --git a/libs/flowbite-angular/utils/icon.list.ts b/libs/flowbite-angular/utils/icon.list.ts
index 190a6567..e883e579 100644
--- a/libs/flowbite-angular/utils/icon.list.ts
+++ b/libs/flowbite-angular/utils/icon.list.ts
@@ -17,12 +17,30 @@ 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 = `
+
+`;
+
export const SUN_SVG_ICON = `