Skip to content

Feat: Added ShadPagination. Implementation of #237#581

Open
asare-21 wants to merge 44 commits intonank1ro:mainfrom
asare-21:feature
Open

Feat: Added ShadPagination. Implementation of #237#581
asare-21 wants to merge 44 commits intonank1ro:mainfrom
asare-21:feature

Conversation

@asare-21
Copy link

@asare-21 asare-21 commented Dec 25, 2025

This PR worked on #237. I have implemented the pagination widget and followed the contribution guidelines

Issues worked on: #237

ShadPagination(
          totalPages: 10,
          initialPage: 1,
          controller: _controller,
          onPageChanged: (value) {
            print('Page changed to ${value + 1}');
          },
        ),

Screenshot of the implementation

Screenshot 2025-12-25 at 16 06 50

Testing this PR

To try this branch, add the following to your pubspec.yaml:

shadcn_ui:
    git:
      url: https://github.com/nank1ro/flutter-shadcn-ui
      ref: feature

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read and followed the Flutter Style Guide.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making.
  • I followed the Data Driven Fixes where supported.
  • All existing and new tests are passing.
  • I bumped the package version following the Semantic Versioning guidelines (For now the major is the second number and the minor the third, because the package is not feature complete). For example, if the package is at version 0.18.0 and you introduced a breaking change or a new feature, bump it to 0.19.0, if you just added a fix or a chore bump it to 0.18.1.
  • I updated the CHANGELOG.md file with a summary of changes made following the format already used.

If you need help, consider asking for advice on Discord.

Summary by CodeRabbit

  • New Features

    • Added a Pagination component with configurable navigation, ellipses, compact/responsive modes, and extensive styling.
    • Added collection utilities for nested maps and deep-copying collections.
  • Theme

    • Added a dedicated pagination theme and integrated it into theme variants and central theme data.
  • Examples

    • Added example and playground pages/routes demonstrating the pagination component.
  • Tests

    • Added comprehensive tests for controller and widget behavior.
  • Chores

    • Updated changelog, metadata, and ignore patterns; minor formatting tweaks.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5742e7f and ed32b35.

📒 Files selected for processing (1)
  • CHANGELOG.md

Walkthrough

Adds a new ShadPagination widget and ShadPaginationController with full pagination UI and logic, introduces ShadPaginationTheme (declaration + generated mixin), wires theme into ShadThemeData and variants, exports the component, adds example/playground pages and route, and adds comprehensive tests and changelog entries.

Changes

Cohort / File(s) Summary
Pagination Component Core
lib/src/components/pagination.dart
New ShadPagination StatefulWidget and ShadPaginationController with selectedIndex state, navigation helpers (next/previous/first/last), page generation (sibling/boundary/ellipsis), responsive/compact modes, styling options, and onPageChanged.
Public API Export & Changelog
lib/shadcn_ui.dart, CHANGELOG.md
Export added for pagination component and changelog updated for 0.45.0 (new component and deepCopy/map helpers noted).
Theme Declaration & Generated Mixin
lib/src/theme/components/pagination.dart, lib/src/theme/components/pagination.g.theme.dart
New ShadPaginationTheme declaration and generated mixin _$ShadPaginationTheme with copyWith, merge, lerp, equality, and interpolation implementations.
Theme Data & Variants Integration
lib/src/theme/data.dart, lib/src/theme/themes/base.dart, lib/src/theme/themes/default_theme_variant.dart, lib/src/theme/themes/default_theme_no_secondary_border_variant.dart
Added paginationTheme to ShadThemeData/ShadBaseTheme, new ShadThemeVariant.paginationTheme() and default variant implementations returning configured ShadPaginationTheme.
Playground & Example Pages / Router
playground/lib/pages/pagination.dart, playground/lib/router.dart, example/lib/pages/pagination.dart
Added PaginationPage/PaginationExample, registered /pagination route, and example page using ShadPagination(totalPages: 10).
Tests
test/src/components/pagination.dart
Comprehensive tests for controller behavior, widget rendering/interactions, responsiveness, edge cases, and assertions.
Playground Config
playground/.gitignore, playground/.metadata
Updated .gitignore to ignore .build/ and .swiftpm/; updated playground metadata revision/platform entries.
Misc — Public API Additions
lib/shadcn_ui.dart (export)
Public export added: src/components/pagination.dart.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • nank1ro

Poem

🐇 I hop from dot to numbered dot,
Buttons gleam and ellipses plot,
Controller nudges, pages run,
Tiny leaps till all are done,
Carrots, clicks — pagination fun!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding the ShadPagination component and addressing issue #237. It is concise, specific, and directly reflects the primary objective of the PR.
Description check ✅ Passed The PR description follows the template structure with issue reference (#237), code example, screenshot, testing instructions, and a completed pre-launch checklist covering all required items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
lib/src/components/pagination.dart (1)

513-514: Consider using dart:math for min/max functions.

The widget defines local min and max helper methods. Dart's dart:math library provides these as top-level functions. Using the standard library functions would be more idiomatic and eliminate the need for custom implementations.

🔎 Proposed change
+import 'dart:math' show max, min;
 import 'package:flutter/material.dart';
 import 'package:shadcn_ui/shadcn_ui.dart';

 // ... rest of file ...

-  T min<T extends num>(T a, T b) => a < b ? a : b;
-  T max<T extends num>(T a, T b) => a > b ? a : b;
 }
playground/lib/pages/pagination.dart (3)

9-9: Add const keyword for consistency.

PaginationExample has a const constructor, so the instantiation should use const.

🔎 Proposed fix
   @override
   Widget build(BuildContext context) {
-    return PaginationExample();
+    return const PaginationExample();
   }

40-40: Remove or update the stale comment.

The comment "Add your pagination widget implementation here" is misleading since the implementation is already present on Line 41.

🔎 Proposed fix
         const SizedBox(height: 20),
-        // Add your pagination widget implementation here
         ShadPagination(

45-47: Consider using debugPrint instead of print.

While print is acceptable for playground examples, debugPrint is the Flutter-recommended alternative as it handles longer outputs and is automatically stripped in release builds.

🔎 Proposed fix
           onPageChanged: (value) {
-            print('Page changed to ${value + 1}');
+            debugPrint('Page changed to ${value + 1}');
           },
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4596fe4 and 3347e2c.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • lib/shadcn_ui.dart
  • lib/src/components/pagination.dart
  • lib/src/theme/components/pagination.dart
  • lib/src/theme/components/pagination.g.theme.dart
  • lib/src/theme/data.dart
  • lib/src/theme/themes/base.dart
  • lib/src/theme/themes/default_theme_no_secondary_border_variant.dart
  • lib/src/theme/themes/default_theme_variant.dart
  • playground/.gitignore
  • playground/.metadata
  • playground/lib/pages/pagination.dart
  • playground/lib/router.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-13T09:57:53.516Z
Learnt from: nank1ro
Repo: nank1ro/flutter-shadcn-ui PR: 352
File: docs/src/content/docs/index.md:129-135
Timestamp: 2025-05-13T09:57:53.516Z
Learning: The shadcn_ui package uses `WidgetStatePropertyAll` in theme configurations (such as `ScrollbarThemeData`) instead of Flutter's native `MaterialStatePropertyAll`. This is intentional and correct according to the package maintainer.

Applied to files:

  • lib/src/theme/themes/default_theme_no_secondary_border_variant.dart
  • lib/src/theme/themes/base.dart
  • lib/src/theme/data.dart
  • lib/src/theme/components/pagination.dart
  • lib/src/theme/themes/default_theme_variant.dart
  • lib/src/theme/components/pagination.g.theme.dart
  • lib/src/components/pagination.dart
🔇 Additional comments (10)
lib/src/theme/data.dart (3)

23-23: LGTM!

The import is correctly placed in alphabetical order among the component imports.


113-113: LGTM!

The pagination theme integration follows the established pattern: accepting an optional paginationTheme parameter in the factory constructor and merging it with the variant's default via effectiveVariant.paginationTheme().merge(paginationTheme). This is consistent with all other theme components in this file.

Also applies to: 245-248


307-307: LGTM!

The internal constructor correctly propagates shadPaginationTheme as a required super parameter.

lib/src/theme/themes/base.dart (3)

22-22: LGTM!

Import correctly placed in alphabetical order.


99-99: LGTM!

The shadPaginationTheme field and constructor parameter are correctly integrated into ShadBaseTheme, following the established pattern for theme components.

Also applies to: 153-153


210-210: LGTM!

The paginationTheme() abstract method is correctly added to ShadThemeVariant, enabling theme variants to provide their pagination theme configuration.

playground/lib/pages/pagination.dart (1)

20-27: LGTM!

The controller lifecycle is properly managed—initialized as a field and disposed in dispose() with the correct call order (_controller.dispose() before super.dispose()).

lib/src/theme/components/pagination.g.theme.dart (1)

1-399: Generated file - no review required.

This is auto-generated code from theme_extensions_builder_annotation. The implementation correctly handles lerp, copyWith, merge, equality, and hashCode for all theme properties.

lib/src/theme/components/pagination.dart (2)

10-53: LGTM!

The ShadPaginationTheme class follows the established pattern for theme components in this package:

  • Uses @themeGen and @immutable annotations
  • Mixes in the generated _$ShadPaginationTheme
  • Implements the canMerge pattern with private backing field
  • Provides sensible defaults for non-nullable properties

The comprehensive set of styling properties enables flexible customization of the pagination component.


175-179: LGTM!

The static lerp method correctly delegates to the generated mixin implementation, following the established pattern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
lib/src/components/pagination.dart (2)

501-509: Consider using a Set to track added pages.

The loop uses pages.contains(i), which is O(n) per iteration, making this section O(n²). For typical pagination with small page counts, the performance impact is negligible. However, for better algorithmic complexity, consider tracking added page numbers in a Set<int> and checking membership there.

🔎 Potential optimization
 List<int?> _generatePageNumbers() {
   if (widget.totalPages <= 0) return [];
 
   final currentPage = controller.selectedIndex + 1; // Convert to 1-based
   final totalPages = widget.totalPages;
   final siblingCount = widget.siblingCount;
   final boundaryCount = widget.boundaryCount;
 
   final pages = <int?>[];
+  final addedPages = <int>{};
 
   // For small number of pages, show all
   if (totalPages <= (boundaryCount * 2 + siblingCount * 2 + 3)) {
     for (var i = 1; i <= totalPages; i++) {
       pages.add(i);
     }
     return pages;
   }
 
   // Always show first boundary pages
   for (var i = 1; i <= min(boundaryCount, totalPages); i++) {
     pages.add(i);
+    addedPages.add(i);
   }
 
   // ... [middle sections would also update addedPages]
 
   // Always show last boundary pages
   for (
     var i = max(totalPages - boundaryCount + 1, boundaryCount + 1);
     i <= totalPages;
     i++
   ) {
-    if (i > boundaryCount && !pages.contains(i)) {
+    if (i > boundaryCount && !addedPages.contains(i)) {
       pages.add(i);
+      addedPages.add(i);
     }
   }
 
   return pages;
 }

654-656: Consider using dart:math for min/max functions.

The custom min and max implementations are correct, but dart:math provides built-in versions that would reduce code and improve maintainability.

🔎 Proposed refactor

Add the import at the top of the file:

 import 'package:flutter/material.dart';
 import 'package:shadcn_ui/shadcn_ui.dart';
+import 'dart:math';

Remove the custom implementations:

-  T min<T extends num>(T a, T b) => a < b ? a : b;
-  T max<T extends num>(T a, T b) => a > b ? a : b;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3347e2c and 08c36ce.

📒 Files selected for processing (1)
  • lib/src/components/pagination.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-13T09:57:53.516Z
Learnt from: nank1ro
Repo: nank1ro/flutter-shadcn-ui PR: 352
File: docs/src/content/docs/index.md:129-135
Timestamp: 2025-05-13T09:57:53.516Z
Learning: The shadcn_ui package uses `WidgetStatePropertyAll` in theme configurations (such as `ScrollbarThemeData`) instead of Flutter's native `MaterialStatePropertyAll`. This is intentional and correct according to the package maintainer.

Applied to files:

  • lib/src/components/pagination.dart
🔇 Additional comments (3)
lib/src/components/pagination.dart (3)

83-98: Parameter validation implemented correctly.

The assertions added here address the previous review comment requesting validation for pagination configuration parameters. All critical parameters (totalPages, initialPage, siblingCount, boundaryCount, buttonHeight, compactBreakpoint) are now validated with clear error messages.


272-284: Controller initialization logic correctly addresses past feedback.

The initialization now respects a provided controller's state while still supporting initialPage when the controller is at its default. The logic only applies initialPage when the provided controller's selectedIndex is 0 and initialPage is non-zero, avoiding silent overwrites of explicit user state.


4-656: Excellent implementation of the pagination component.

The ShadPagination widget is well-architected with:

  • Proper resource management (controller lifecycle)
  • Comprehensive theming integration
  • Responsive design with compact mode support
  • Accessible UI (disabled states, tooltips)
  • Flexible page number generation with ellipsis logic
  • Extensive customization options

The implementation addresses all previously raised concerns and follows the package's established patterns.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 27, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
test/src/components/pagination.dart (1)

327-365: Consider resetting test view dimensions after modification.

Tests at lines 327-344 and 346-364 modify tester.view.physicalSize but don't reset it afterward. While Flutter test framework typically handles cleanup, explicitly resetting the view ensures test isolation and prevents potential interference with subsequent tests.

🔎 Suggested approach to reset view

Add an addTearDown call or reset the view at the end of each test:

  testWidgets('switches to compact mode below breakpoint', (tester) async {
    // Set screen width below default breakpoint (768)
    tester.view.physicalSize = const Size(500, 800);
    tester.view.devicePixelRatio = 1.0;
+   addTearDown(() {
+     tester.view.resetPhysicalSize();
+     tester.view.resetDevicePixelRatio();
+   });

    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: ShadPagination(
            totalPages: 10,
          ),
        ),
      ),
    );

    // Should be in compact mode due to screen width
    expect(find.byType(ShadButton), findsAtLeast(3));
  });

Apply similar changes to the test at lines 346-364.

lib/src/components/pagination.dart (3)

278-283: Clarify the initialPage vs. external controller precedence.

The logic attempts to apply initialPage only when the provided controller is at the default value (0), but this creates ambiguous behavior:

  • If a user provides a controller at index 0 and wants to use initialPage=0, the condition passes but nothing happens (correct by coincidence).
  • If a user provides a controller at index 3 and also passes initialPage=5, the initialPage is silently ignored (potentially surprising).
  • Index 0 is a valid page selection, so treating it as "uninitialized" is assumption-based.

Consider one of these clearer approaches:

  1. Always respect external controller (recommended): Document that initialPage only applies when no controller is provided.
  2. Always apply initialPage: Document that initialPage overrides the controller's initial state.
  3. Add an assertion: Fail fast if both a non-null controller and a non-zero initialPage are provided together.
🔎 Option 1 (recommended): Only use initialPage with internal controller
  @override
  void initState() {
    super.initState();
    if (widget.controller == null) {
      _controller = ShadPaginationController();
      _controller.selectedIndex = widget.initialPage;
-   } else {
-     // Respect the provided controller's state, but apply initialPage
-     // if it's at default
-     if (widget.controller!.selectedIndex == 0 && widget.initialPage != 0) {
-       widget.controller!.selectedIndex = widget.initialPage;
-     }
    }
+   // When an external controller is provided, respect its state entirely
+   // and ignore initialPage
  }

Update the initialPage documentation to clarify:

/// The initial page index.
/// Only used when [controller] is null. If you provide an external
/// controller, set its selectedIndex before passing it to ShadPagination.
final int initialPage;
🔎 Option 3: Fail fast with assertion

Add an assertion in the constructor:

  const ShadPagination({
    // ... parameters
  }) : assert(totalPages > 0, 'totalPages must be greater than 0'),
       // ... other assertions
+       assert(
+         controller == null || initialPage == 0,
+         'Cannot specify both controller and non-zero initialPage. '
+         'Either provide a controller with pre-set selectedIndex, '
+         'or use initialPage without a controller.',
+       ),
       assert(boundaryCount >= 0, 'boundaryCount must be non-negative');

500-509: Consider simplifying the boundary page logic to avoid potential duplicates.

The loop at lines 501-509 adds the last boundary pages, but it uses pages.contains(i) to avoid duplicates. This check is O(n) for each iteration and suggests the logic might add pages that were already included. Using a Set or clearer range logic would be more efficient and clearer.

Additionally, the loop range max(totalPages - boundaryCount + 1, boundaryCount + 1) is complex and could have edge cases that are hard to reason about.

🔎 Alternative approach using Set for deduplication
  List<int?> _generatePageNumbers() {
    if (widget.totalPages <= 0) return [];

    final currentPage = controller.selectedIndex + 1; // Convert to 1-based
    final totalPages = widget.totalPages;
    final siblingCount = widget.siblingCount;
    final boundaryCount = widget.boundaryCount;

+   final pageSet = <int>{};
    final pages = <int?>[];

    // For small number of pages, show all
    if (totalPages <= (boundaryCount * 2 + siblingCount * 2 + 3)) {
      for (var i = 1; i <= totalPages; i++) {
        pages.add(i);
      }
      return pages;
    }

    // Collect all pages that should be shown (without ellipsis)
    // Add first boundary pages
    for (var i = 1; i <= min(boundaryCount, totalPages); i++) {
-     pages.add(i);
+     pageSet.add(i);
    }

    // Add current page and siblings
    final start = max(currentPage - siblingCount, 1);
    final end = min(currentPage + siblingCount, totalPages);
    for (var i = start; i <= end; i++) {
-     if (i > boundaryCount && i <= totalPages - boundaryCount) {
-       pages.add(i);
-     }
+     pageSet.add(i);
    }

    // Add last boundary pages
    for (var i = max(totalPages - boundaryCount + 1, 1); i <= totalPages; i++) {
-     if (i > boundaryCount && !pages.contains(i)) {
-       pages.add(i);
-     }
+     pageSet.add(i);
    }

+   // Convert set to list with ellipses
+   final sortedPages = pageSet.toList()..sort();
+   for (var i = 0; i < sortedPages.length; i++) {
+     if (i > 0 && sortedPages[i] - sortedPages[i - 1] > 1 && widget.showEllipsis) {
+       pages.add(null); // Add ellipsis
+     }
+     pages.add(sortedPages[i]);
+   }

-   // Add ellipsis logic...

    return pages;
  }

This approach is clearer: collect all pages in a Set (which handles duplicates automatically), sort them, then insert ellipses where there are gaps.


634-639: Consider disabling the selected page button.

Lines 634-639 make the button's onPressed callback check if (!isSelected) before updating, but the button itself is always pressable. This means users can tap the currently selected page, and nothing happens—which might feel unresponsive or buggy.

Consider setting onPressed: null for selected pages to visually indicate they're not actionable.

🔎 Proposed change
    return ShadButton.raw(
      variant: effectiveVariant,
      size: widget.buttonSize ?? ShadButtonSize.sm,
      padding:
          widget.buttonPadding ??
          const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      backgroundColor: effectiveBackgroundColor,
-     onPressed: () {
-       if (!isSelected) {
-         controller.selectedIndex = page - 1;
-         widget.onPageChanged?.call(controller.selectedIndex);
-       }
-     },
+     onPressed: isSelected
+         ? null
+         : () {
+             controller.selectedIndex = page - 1;
+             widget.onPageChanged?.call(controller.selectedIndex);
+           },
      child: Container(
        height: widget.buttonHeight,
        alignment: Alignment.center,
        child: Text(
          page.toString(),
          style: TextStyle(
            fontSize: 14,
            color: isSelected ? effectiveForegroundColor : null,
          ),
        ),
      ),
    );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08c36ce and 9dce55b.

⛔ Files ignored due to path filters (2)
  • test/src/components/failures/input_masterImage.png is excluded by !**/*.png
  • test/src/components/failures/input_testImage.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • lib/src/components/pagination.dart
  • test/src/components/pagination.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-13T09:57:53.516Z
Learnt from: nank1ro
Repo: nank1ro/flutter-shadcn-ui PR: 352
File: docs/src/content/docs/index.md:129-135
Timestamp: 2025-05-13T09:57:53.516Z
Learning: The shadcn_ui package uses `WidgetStatePropertyAll` in theme configurations (such as `ScrollbarThemeData`) instead of Flutter's native `MaterialStatePropertyAll`. This is intentional and correct according to the package maintainer.

Applied to files:

  • lib/src/components/pagination.dart
🔇 Additional comments (1)
lib/src/components/pagination.dart (1)

326-327: No issues found. The EdgeInsetsGeometry.horizontal property is a standard Flutter API and works as expected. No changes needed.

asare-21 and others added 2 commits December 27, 2025 22:21
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 27, 2025
@@ -0,0 +1,707 @@
import 'package:flutter/material.dart';
Copy link
Owner

Choose a reason for hiding this comment

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

Do not import material, tests will fail.

@@ -0,0 +1,707 @@
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
Copy link
Owner

Choose a reason for hiding this comment

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

Do not import the barrel file, it is meant only for library users.
Import each component individually.

Comment on lines +53 to +81
this.initialPage = 0,
this.padding,
this.border,
this.backgroundColor,
this.radius,
this.onPageChanged,
this.showEllipsis = true,
this.showFirstLastButtons = false,
this.isCompact = false,
this.showPageNumbers = true,
this.siblingCount = 1,
this.boundaryCount = 1,
this.buttonVariant,
this.buttonSize,
this.buttonPadding,
this.selectedButtonVariant,
this.selectedButtonBackgroundColor,
this.buttonGap = 4.0,
this.buttonHeight = 40.0,
this.compactBreakpoint = 768.0,
this.compactButtonVariant = ShadButtonVariant.ghost,
this.ellipsisColor,
this.navigationButtonSize,
this.navigationButtonVariant,
this.selectedButtonForegroundColor,
this.width,
this.margin = const EdgeInsets.symmetric(horizontal: 16),
this.previousButtonLabel = 'Previous',
this.nextButtonLabel = 'Next',
Copy link
Owner

Choose a reason for hiding this comment

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

Do not pass default values here, follow the contributing guide.

const EdgeInsets.all(4);

final effectiveMargin =
widget.margin ?? const EdgeInsets.symmetric(horizontal: 16);
Copy link
Owner

Choose a reason for hiding this comment

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

where's the theme?

theme.shadPaginationTheme.compactBreakpoint;

final isCompactBasedOnBreakpoint =
widget.isCompact || screenWidth < effectiveCompactBreakpoint;
Copy link
Owner

Choose a reason for hiding this comment

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

missing theme prop retrieval

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
Copy link
Owner

Choose a reason for hiding this comment

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

Please add a pagination example in the example folder too.

Co-authored-by: Alexandru Mariuti <alex@mariuti.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 10, 2026
Co-authored-by: Alexandru Mariuti <alex@mariuti.com>
asare-21 and others added 2 commits January 10, 2026 19:24
Co-authored-by: Alexandru Mariuti <alex@mariuti.com>
Co-authored-by: Alexandru Mariuti <alex@mariuti.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In @lib/src/theme/components/pagination.dart:
- Around line 13-54: Constructor ShadPaginationTheme allows invalid numeric
values that break layouts; add constructor asserts in ShadPaginationTheme to
enforce siblingCount >= 0, boundaryCount >= 0, compactBreakpoint > 0, and
(buttonHeight == null || buttonHeight > 0) and (buttonGap == null || buttonGap
>= 0) so invalid theme states are rejected early; update the
ShadPaginationTheme(...) initializer to include these assert(...) checks
referencing the constructor and the fields siblingCount, boundaryCount,
compactBreakpoint, buttonHeight, and buttonGap.

In @test/src/components/pagination.dart:
- Around line 104-119: The test 'respects initialPage' only asserts that a
button with label '3' exists but doesn't verify it's the selected page; update
the test for ShadPagination to assert selection by either supplying an
onPageChanged callback to capture the active page (use initialPage to assert the
initial selected index) or by querying the actual pagination button widget/state
(e.g., checking a selection flag or selected styling for the widget found by
find.text('3')); modify the test to set onPageChanged or inspect the button
widget to confirm the initially selected page is the one corresponding to
initialPage.
- Around line 251-267: Add a complementary test to verify the next button is
disabled on the last page by instantiating ShadPagination with initialPage: 4
(zero-based) and asserting the ShadButton that contains Icons.chevron_right has
a null onPressed; avoid the fragile .first on find.byIcon by using a more
specific finder such as find.widgetWithIcon(ShadButton, Icons.chevron_right) (or
.last if appropriate) so the test targets the correct button.
- Around line 289-304: The test "boundaryCount controls boundary pages" only
asserts start boundaries; update the test in testWidgets to also assert the end
boundaries for ShadPagination with totalPages: 10 and boundaryCount: 2 by adding
expectations that the labels '9' and '10' are present (e.g.,
expect(find.text('9'), findsOneWidget) and expect(find.text('10'),
findsOneWidget)) so both start and end boundary pages are verified.
- Around line 327-364: Both responsive tests change
tester.view.physicalSize/devicePixelRatio and never reset them, which can
pollute other tests; add a tearDown (or addTearDown) in these test cases (or
globally for the file) to restore the original tester.view.physicalSize and
tester.view.devicePixelRatio after each test, specifically around the
testWidgets named 'switches to compact mode below breakpoint' and 'respects
custom compactBreakpoint' that exercise ShadPagination so subsequent tests run
with the default screen dimensions.
- Around line 234-249: Fix the missing space in the inline comment and replace
the weak assertion in the testWidgets('compact mode shows fewer buttons') for
ShadPagination: ensure the comment reads "// should be visible", and strengthen
the test by asserting exact compact-mode behavior — e.g., assert
find.byType(ShadButton) returns the expected exact count for compact mode (prev,
current, next) and also assert that distant page-number buttons are not present
(e.g., expect(find.text('10'), findsNothing) or expect(find.text('1'),
findsNothing)) or that an ellipsis indicator (Text('...') or appropriate widget)
is present to prove truncation rather than using findsAtLeast(3).
- Around line 269-287: The test 'siblingCount controls visible siblings' for
ShadPagination is missing an assertion for page 8; update the test to also
assert that the text '8' is visible given initialPage: 5, siblingCount: 2 (and
default boundaryCount: 1) by adding an expectation for find.text('8') to be
found (e.g., expect(find.text('8'), findsOneWidget)); keep the rest of the setup
unchanged.
🧹 Nitpick comments (2)
example/lib/pages/pagination.dart (1)

1-15: Consider showcasing onPageChanged / controller in the example page (currently static).

ShadPagination(totalPages: 10) is fine for a minimal demo, but the example page would be more helpful if it also demonstrated handling page changes (and optionally controller-driven updates).

test/src/components/pagination.dart (1)

197-211: Consider using semantic identifiers instead of specific icons.

The test verifies first/last buttons by checking for Icons.chevron_left and Icons.chevron_right, which couples the test to implementation details. If icon choices change, the test will break even if functionality remains correct.

🔧 Alternative approach

Consider using semantic labels or keys for navigation buttons:

// In production code:
ShadButton(
  key: const Key('pagination_previous'),
  // ...
)

// In test:
expect(find.byKey(const Key('pagination_previous')), findsOneWidget);
expect(find.byKey(const Key('pagination_next')), findsOneWidget);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 151c067 and 9f1597c.

📒 Files selected for processing (4)
  • example/lib/pages/pagination.dart
  • lib/src/components/pagination.dart
  • lib/src/theme/components/pagination.dart
  • test/src/components/pagination.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/src/components/pagination.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-13T09:57:53.516Z
Learnt from: nank1ro
Repo: nank1ro/flutter-shadcn-ui PR: 352
File: docs/src/content/docs/index.md:129-135
Timestamp: 2025-05-13T09:57:53.516Z
Learning: The shadcn_ui package uses `WidgetStatePropertyAll` in theme configurations (such as `ScrollbarThemeData`) instead of Flutter's native `MaterialStatePropertyAll`. This is intentional and correct according to the package maintainer.

Applied to files:

  • lib/src/theme/components/pagination.dart
🔇 Additional comments (11)
lib/src/theme/components/pagination.dart (2)

179-183: lerp delegation to the generated implementation looks good.


8-12: No issues found. The codegen is properly set up with the @themeGen annotation and part 'pagination.g.theme.dart' directive, the generated mixin structure is correct, and all dartdoc macros like ShadPagination.radius and ShadPagination.padding are properly defined with matching @template/@endtemplate pairs.

test/src/components/pagination.dart (9)

6-85: LGTM! Comprehensive controller test coverage.

The controller tests thoroughly verify initialization, state updates, listener notifications, and all navigation methods with proper boundary handling.


88-102: LGTM!

Basic rendering test correctly verifies all page numbers are displayed.


121-142: LGTM!

Correctly verifies the onPageChanged callback with proper 0-based index conversion.


144-165: LGTM!

Correctly verifies external controller integration.


167-195: LGTM!

Ellipsis visibility tests are well-structured.


213-232: LGTM!

Correctly verifies that showPageNumbers: false hides all page number text.


306-323: LGTM! Good assertion error coverage.

Properly verifies that invalid totalPages and initialPage values throw assertion errors.


367-412: LGTM! Excellent edge case coverage.

The tests properly handle single page, very large page counts, and dynamic updates. The _TestPageUpdater helper widget is a clean approach to testing dynamic behavior.


415-437: LGTM! Clean helper widget.

The _TestPageUpdater widget is well-designed for testing dynamic state changes.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
@asare-21 asare-21 requested a review from nank1ro January 11, 2026 08:36
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 15, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 20, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants