Skip to content

Conversation

MahmudE14
Copy link

@MahmudE14 MahmudE14 commented Sep 2, 2025

This PR adds a component to handle pagination. This component can seamlessly handle pagination of data that is is Laravel's pagination format.

Key Features

  • Multiple styling options (fill, outline, link)
  • Configurable alignment (start, center, end)
  • Optional info display showing current range
  • Smart rendering (renders based on next page and previous page availability)

Summary by CodeRabbit

  • New Features

    • Added a Pagination component with configurable alignment (start/center/end), button styles (fill/outline/link), optional results info, and choice of link or button controls. Includes accessible labels and proper disabled states.
  • Documentation

    • Added README with usage, API details, and examples.
    • Introduced Storybook stories showcasing multiple states and styles (basic, with info, centered, outline/link styles, first/last/middle pages, buttons, minimal).
  • Chores

    • Added index export to simplify imports of the Pagination component.

Copy link

vercel bot commented Sep 2, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
tail-react Error Error Sep 2, 2025 3:36pm

Copy link

coderabbitai bot commented Sep 2, 2025

Walkthrough

Introduces a new Pagination React component with props for alignment, info display, button style, and element type. Adds Storybook stories demonstrating multiple pagination states, a README documenting usage and API, and an index file re-exporting the component.

Changes

Cohort / File(s) Summary
Component Implementation
src/.../Pagination.tsx
Adds a generic Pagination component rendering prev/next and numbered links, optional info text, alignment control, button styles, and anchor/button rendering. Exits early when no navigation available.
Storybook Stories
src/.../Pagination/Pagination.stories.tsx
Introduces meta config and multiple stories (Basic, WithInfo, Centered, OutlineStyle, LinkStyle, FirstPage, LastPage, MiddlePage, AsButtons, Minimal) using a mock data builder to simulate various pagination scenarios.
Documentation
src/.../Pagination/README.md
Adds README detailing props, data shapes, usage examples, behavior, and accessibility notes.
Barrel Export
src/.../Pagination/index.ts
Adds barrel export re-exporting Pagination.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

hop-hop! my paws tap page by page,
a trail of links across the stage—
first, next, and numbers gleam in line,
centered, filled, or outlined fine.
i nibble docs, then stories spin—
click, we leap; last page, we win!
(•̀ᴗ•́)و ✦

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@MahmudE14 MahmudE14 changed the title feat: table pagination feat: pagination component Sep 2, 2025
Copy link

@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 (10)
src/Components/Pagination/index.ts (1)

1-1: Barrel export looks good; consider re-exporting types (optional).

If you decide to expose the data types, re-export them here for consumers.

 export * from './Pagination';
+export type { PaginationData, Link } from './Pagination';
src/Components/Pagination/README.md (2)

55-57: Use public import path in examples.

Show consumers how to import from the package (or the component barrel), not a relative file path.

-import { Pagination } from './Pagination';
+import { Pagination } from '@wedevs/tail-react'; // or 'src/Components/Pagination'

124-133: Accessibility claims vs. implementation details.

To truly meet these, document that disabled anchors are marked aria-disabled and removed from tab order (tabIndex -1), or rendered as buttons when interactive without hrefs.

Would you like a follow-up doc patch after the component change below?

src/Components/Pagination/Pagination.tsx (5)

41-42: Don’t hide the info row when there’s only one page.

Render the info section even when there is no prev/next; conditionally render only the nav.

-  if (!data || (!data.next_page_url && !data.prev_page_url)) return null;
+  if (!data) return null;
+  const hasNav =
+    Boolean(data.next_page_url || data.prev_page_url) ||
+    (Array.isArray(data.links) && data.links.length > 0);
...
-  return (
+  return (
     <div className="flex w-full flex-col items-center justify-center md:flex-row md:justify-between">
       <Info showInfo={showInfo} />
 
-      <nav
+      {hasNav && (
+      <nav
         className={`flex w-full flex-wrap items-center justify-center space-x-2 ${getAlignStyle()}`}
         aria-label="Pagination Navigation"
       >
         ...
-      </nav>
+      </nav>
+      )}
     </div>
   );

Also applies to: 76-79, 80-83, 126-128


52-57: Make page link filtering robust and locale-agnostic.

Rely on numeric labels rather than HTML entities/ellipsis strings that can vary.

-  const pageLinks = links.filter(
-    link =>
-      !link.label.includes('&laquo;') &&
-      !link.label.includes('&raquo;') &&
-      link.label !== '...',
-  );
+  const pageLinks = links.filter(link => /^\d+$/.test(link.label));

66-74: Info component: return null and handle null from/to gracefully.

Avoid empty fragments and show 0-based fallbacks.

-  const Info = ({ showInfo }: { showInfo: boolean }) => {
-    if (!showInfo) return <></>;
+  const Info = ({ showInfo }: { showInfo: boolean }) => {
+    if (!showInfo) return null;
 
     return (
       <div className="mb-2 w-full text-center text-sm text-gray-600 sm:mb-0 md:text-left" aria-live="polite">
-        Showing {from} to {to} of {total} entries
+        Showing {from ?? 0} to {to ?? 0} of {total} entries
       </div>
     );
   };

97-104: Avoid index as key.

Use a stable key to prevent reconciliation issues when the link set changes.

-        {pageLinks.map((link, index) => (
+        {pageLinks.map((link) => (
           <Button
-            key={index}
+            key={link.url ?? `p-${link.label}`}

84-96: Optional i18n for “Previous”/“Next”.

Expose prevLabel/nextLabel props (defaulting to “Previous”/“Next”) to allow localization.

If you’d like, I can draft the minimal prop wiring.

Also applies to: 114-126

src/Components/Pagination/Pagination.stories.tsx (2)

127-139: Let the component do the prev/next filtering; keep stories closer to Laravel payloads.

Returning links without prev/next skips exercising your in-component filter. Keep them and let the component filter.

-    links: links.filter(link => !link.label.includes('&laquo;') && !link.label.includes('&raquo;')),
+    links,

32-42: Annotate the mock with the exported type.

After exporting PaginationData/Link, type createMockData’s return for stronger story type-safety.

-const createMockData = (currentPage: number, totalPages: number, showEllipsis = false) => {
+import type { PaginationData } from './Pagination';
+const createMockData = (
+  currentPage: number,
+  totalPages: number,
+  showEllipsis = false
+): PaginationData<{ id: number; name: string }> => {
...
-  return {
+  return {

Also applies to: 122-139

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd5d3e and d85e536.

📒 Files selected for processing (4)
  • src/Components/Pagination/Pagination.stories.tsx (1 hunks)
  • src/Components/Pagination/Pagination.tsx (1 hunks)
  • src/Components/Pagination/README.md (1 hunks)
  • src/Components/Pagination/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/Components/Pagination/Pagination.tsx (1)
src/Components/Button/Button.tsx (1)
  • Button (149-149)
src/Components/Pagination/Pagination.stories.tsx (1)
src/Components/Pagination/Pagination.tsx (1)
  • Pagination (34-129)
🪛 LanguageTool
src/Components/Pagination/README.md

[grammar] ~128-~128: There might be a mistake here.
Context: ... <nav> element with proper ARIA labels - Provides aria-current="page" for the a...

(QB_NEW_EN)


[grammar] ~129-~129: There might be a mistake here.
Context: ...aria-current="page"for the active page - Includes descriptivearia-label` attrib...

(QB_NEW_EN)


[grammar] ~130-~130: There might be a mistake here.
Context: ... aria-label attributes for all buttons - Maintains proper focus management and ke...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ...focus management and keyboard navigation - Screen reader friendly with live region ...

(QB_NEW_EN)


[grammar] ~138-~138: There might be a mistake here.
Context: ...nt variants (primary, secondary, danger) - Multiple button styles (fill, outline, l...

(QB_NEW_EN)


[grammar] ~139-~139: There might be a mistake here.
Context: ...iple button styles (fill, outline, link) - Consistent disabled states and visual fe...

(QB_NEW_EN)


[grammar] ~140-~140: There might be a mistake here.
Context: ...tent disabled states and visual feedback - Proper focus and hover states ## Common...

(QB_NEW_EN)


[grammar] ~206-~206: There might be a mistake here.
Context: ...- start: Aligns pagination to the left - center: Centers the pagination controls - `end...

(QB_NEW_EN)


[grammar] ~207-~207: There might be a mistake here.
Context: ...center: Centers the pagination controls - end`: Aligns pagination to the right (defaul...

(QB_NEW_EN)


[grammar] ~212-~212: There might be a mistake here.
Context: ...ill: Solid background buttons (default) - outline: Border-only buttons - link`: Minimal ...

(QB_NEW_EN)


[grammar] ~213-~213: There might be a mistake here.
Context: ...efault) - outline: Border-only buttons - link: Minimal link-style buttons ## Best Pr...

(QB_NEW_EN)


[grammar] ~234-~234: There might be a mistake here.
Context: ...hat support: - ES6+ JavaScript features - CSS Grid and Flexbox - Modern React feat...

(QB_NEW_EN)


[grammar] ~235-~235: There might be a mistake here.
Context: ...vaScript features - CSS Grid and Flexbox - Modern React features (hooks, etc.) For...

(QB_NEW_EN)

Comment on lines +10 to +24
interface Pagination<T = Record<string, unknown>> {
data: T[];
links: Link[];
current_page: number;
last_page: number;
first_page_url?: string;
last_page_url?: string;
next_page_url?: string | null;
prev_page_url?: string | null;
path?: string;
per_page: number;
total: number;
from: number;
to: number;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Type naming and Laravel nullability; export types for consumers.

  • Rename the Pagination interface to PaginationData to avoid confusion with the component.
  • Make from/to nullable to match Laravel responses.
  • Optionally export Link and PaginationData.
-interface Link {
+export interface Link {
   label: string;
   url: string | null;
   active: boolean;
   page: number | null;
 }
 
-interface Pagination<T = Record<string, unknown>> {
+export interface PaginationData<T = Record<string, unknown>> {
   data: T[];
   links: Link[];
   current_page: number;
   last_page: number;
   first_page_url?: string;
   last_page_url?: string;
   next_page_url?: string | null;
   prev_page_url?: string | null;
   path?: string;
   per_page: number;
   total: number;
-  from: number;
-  to: number;
+  from: number | null;
+  to: number | null;
 }
 
-interface PaginationProps<T = Record<string, unknown>> {
-  data: Pagination<T>;
+interface PaginationProps<T = Record<string, unknown>> {
+  data: PaginationData<T>;
   align?: 'start' | 'center' | 'end';
   showInfo?: boolean;
   buttonStyle?: 'fill' | 'outline' | 'link';
   paginationButtonAs?: 'a' | 'button';
 }

Also applies to: 26-33

🤖 Prompt for AI Agents
In src/Components/Pagination/Pagination.tsx around lines 10-24 (and also apply
same changes to lines 26-33), rename the interface Pagination to PaginationData
to avoid name collision with the component, change the types of from and to to
allow null (e.g., number | null) to match Laravel nullable fields, and export
the Link type and the new PaginationData interface so other modules can consume
them; update any local references to use the new PaginationData name.

Comment on lines +84 to +96
<Button
variant="secondary"
style={buttonStyle}
size="medium"
disabled={!prev_page_url}
as={paginationButtonAs}
href={prev_page_url || '#'}
className={!prev_page_url ? 'pointer-events-none' : ''}
aria-label="Go to previous page"
>
Previous
</Button>

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Disabled anchors: prevent keyboard activation and improve a11y.

Anchors with href="#" remain focusable/activatable by keyboard. Use aria-disabled and remove from tab order when no URL; avoid "#" altogether.

         <Button
           variant="secondary"
           style={buttonStyle}
           size="medium"
-          disabled={!prev_page_url}
-          as={paginationButtonAs}
-          href={prev_page_url || '#'}
-          className={!prev_page_url ? 'pointer-events-none' : ''}
+          disabled={!prev_page_url}
+          as={paginationButtonAs}
+          href={prev_page_url ?? undefined}
+          aria-disabled={!prev_page_url || undefined}
+          tabIndex={!prev_page_url ? -1 : undefined}
+          className={!prev_page_url ? 'pointer-events-none' : ''}
           aria-label="Go to previous page"
         >
           Previous
         </Button>
...
         <Button
           variant="secondary"
           style={buttonStyle}
           size="medium"
-          disabled={!next_page_url}
-          as={paginationButtonAs}
-          href={next_page_url || '#'}
-          className={!next_page_url ? 'pointer-events-none' : ''}
+          disabled={!next_page_url}
+          as={paginationButtonAs}
+          href={next_page_url ?? undefined}
+          aria-disabled={!next_page_url || undefined}
+          tabIndex={!next_page_url ? -1 : undefined}
+          className={!next_page_url ? 'pointer-events-none' : ''}
           aria-label="Go to next page"
         >
           Next
         </Button>

Also applies to: 114-126

🤖 Prompt for AI Agents
In src/Components/Pagination/Pagination.tsx around lines 84-96 (and similarly
for lines 114-126), the Button renders as an anchor with href="#" when no page
URL which keeps it focusable and keyboard-activatable; remove the placeholder
href when prev_page_url/next_page_url is falsy, add aria-disabled="true" and
tabIndex={-1} to remove it from the tab order, and keep the visual disabled
styling (e.g. pointer-events-none/className) so the element is not
keyboard-activated; ensure when a real URL exists you restore the href, remove
aria-disabled, and set a normal tabIndex.

Comment on lines +28 to +50
interface Pagination<T = Record<string, unknown>> {
data: T[]; // Array of items for the current page
links: Link[]; // Array of pagination links
current_page: number; // Current page number
last_page: number; // Total number of pages
first_page_url?: string; // URL for the first page
last_page_url?: string; // URL for the last page
next_page_url?: string | null; // URL for the next page
prev_page_url?: string | null; // URL for the previous page
path?: string; // Base path for pagination URLs
per_page: number; // Number of items per page
total: number; // Total number of items
from: number; // First item number on current page
to: number; // Last item number on current page
}

interface Link {
label: string; // Display label for the link
url: string | null; // URL for the link (null if disabled)
active: boolean; // Whether this is the current page
page: number | null; // Page number (null for Previous/Next)
}
```
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Rename the interface to avoid value/type name collision and align with Laravel nullability.

Laravel’s from/to can be null on empty results. Also, using the same name as the component is confusing.

-interface Pagination<T = Record<string, unknown>> {
+interface PaginationData<T = Record<string, unknown>> {
   data: T[];                    // Array of items for the current page
   links: Link[];               // Array of pagination links
   current_page: number;        // Current page number
   last_page: number;           // Total number of pages
   first_page_url?: string;     // URL for the first page
   last_page_url?: string;      // URL for the last page
   next_page_url?: string | null; // URL for the next page
   prev_page_url?: string | null; // URL for the previous page
   path?: string;               // Base path for pagination URLs
   per_page: number;            // Number of items per page
   total: number;               // Total number of items
-  from: number;                // First item number on current page
-  to: number;                  // Last item number on current page
+  from: number | null;         // First item number on current page (nullable)
+  to: number | null;           // Last item number on current page (nullable)
 }
📝 Committable suggestion

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

Suggested change
interface Pagination<T = Record<string, unknown>> {
data: T[]; // Array of items for the current page
links: Link[]; // Array of pagination links
current_page: number; // Current page number
last_page: number; // Total number of pages
first_page_url?: string; // URL for the first page
last_page_url?: string; // URL for the last page
next_page_url?: string | null; // URL for the next page
prev_page_url?: string | null; // URL for the previous page
path?: string; // Base path for pagination URLs
per_page: number; // Number of items per page
total: number; // Total number of items
from: number; // First item number on current page
to: number; // Last item number on current page
}
interface Link {
label: string; // Display label for the link
url: string | null; // URL for the link (null if disabled)
active: boolean; // Whether this is the current page
page: number | null; // Page number (null for Previous/Next)
}
```
interface PaginationData<T = Record<string, unknown>> {
data: T[]; // Array of items for the current page
links: Link[]; // Array of pagination links
current_page: number; // Current page number
last_page: number; // Total number of pages
first_page_url?: string; // URL for the first page
last_page_url?: string; // URL for the last page
next_page_url?: string | null; // URL for the next page
prev_page_url?: string | null; // URL for the previous page
path?: string; // Base path for pagination URLs
per_page: number; // Number of items per page
total: number; // Total number of items
from: number | null; // First item number on current page (nullable)
to: number | null; // Last item number on current page (nullable)
}
🤖 Prompt for AI Agents
In src/Components/Pagination/README.md around lines 28 to 50, the exported
interface name collides with the component name and the from/to fields should be
nullable to match Laravel; rename the interface to something like
PaginatedResponse or PaginationData (update any references/imports accordingly)
and change the types of from and to from number to number | null to reflect
empty-result cases; ensure any usage sites or docs are updated to the new name
and nullable types.

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.

1 participant