PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / doc-categories / components / Pagination.js

Pagination.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/doc-categories/components/Pagination.js

168 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React from "react";
2 import { __ } from "@wordpress/i18n";
3
4 const FirstIcon = () => (
5 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
6 <path d="M11 17 5 12l6-5" />
7 <path d="M18 17 12 12l6-5" />
8 </svg>
9 );
10
11 const PrevIcon = () => (
12 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
13 <path d="m15 18-6-6 6-6" />
14 </svg>
15 );
16
17 const NextIcon = () => (
18 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
19 <path d="m9 6 6 6-6 6" />
20 </svg>
21 );
22
23 const LastIcon = () => (
24 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
25 <path d="M6 17 12 12 6 7" />
26 <path d="M13 17 19 12 13 7" />
27 </svg>
28 );
29
30 const ChevronDownIcon = () => (
31 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
32 <path d="m6 9 6 6 6-6" />
33 </svg>
34 );
35
36 /**
37 * Build per-page options dynamically from the total.
38 * Base is always [10, 20, 50], then adds multiples of 100 until the last
39 * option is >= total (so there's always one option that fits everything).
40 * Examples: 70 → [10,20,50,100] | 645 → [10,20,50,100,200,…,700]
41 */
42 function buildPerPageOptions(total) {
43 const options = [10, 20, 50];
44 let n = 100;
45 while (options[options.length - 1] < total) {
46 options.push(n);
47 n += 100;
48 }
49 return options;
50 }
51
52 const buildPageList = (page, totalPages) => {
53 if (totalPages <= 7) {
54 return Array.from({ length: totalPages }, (_, i) => i + 1);
55 }
56 const pages = [1];
57 if (page > 3) pages.push("…");
58 for (let i = Math.max(2, page - 1); i <= Math.min(totalPages - 1, page + 1); i++) {
59 pages.push(i);
60 }
61 if (page < totalPages - 2) pages.push("…");
62 pages.push(totalPages);
63 return pages;
64 };
65
66 const Pagination = ({ total, page, perPage, onPageChange, onPerPageChange }) => {
67 const totalPages = Math.ceil(total / perPage);
68 // Hide the whole pagination (info + rows-per-page + nav) on a single page.
69 if (!total || totalPages <= 1) return null;
70
71 const showNav = totalPages > 1;
72 const perPageOptions = buildPerPageOptions(total);
73
74 const start = (page - 1) * perPage + 1;
75 const end = Math.min(page * perPage, total);
76 const pageList = buildPageList(page, totalPages);
77
78 const handlePerPageChange = (e) => {
79 onPerPageChange(Number(e.target.value));
80 onPageChange(1);
81 };
82
83 return (
84 <div className="bd-dc-pagination">
85 {/* Left: "Showing X–Y of Z" + divider + Rows per page */}
86 <div className="bd-dc-pagination-left">
87 <span className="bd-dc-pagination-info">
88 {__("Showing", "betterdocs")}{" "}
89 <strong>{start}–{end}</strong>{" "}
90 {__("of", "betterdocs")}{" "}
91 <strong>{total}</strong>
92 </span>
93
94 <span className="bd-dc-pagination-divider" aria-hidden="true" />
95
96 <div className="bd-dc-rows-per-page">
97 <span className="bd-dc-rows-label">{__("Rows per page", "betterdocs")}</span>
98 <div className="bd-dc-per-page-wrapper">
99 <select
100 className="bd-dc-per-page-select"
101 value={perPage}
102 onChange={handlePerPageChange}
103 aria-label={__("Rows per page", "betterdocs")}
104 >
105 {perPageOptions.map((n) => (
106 <option key={n} value={n}>{n}</option>
107 ))}
108 </select>
109 <span className="bd-dc-per-page-chevron" aria-hidden="true">
110 <ChevronDownIcon />
111 </span>
112 </div>
113 </div>
114 </div>
115
116 {/* Right: first/prev + page numbers (with ellipsis) + next/last */}
117 {showNav && <div className="bd-dc-pagination-nav">
118 <button
119 type="button"
120 className="bd-dc-page-btn"
121 disabled={page === 1}
122 onClick={() => onPageChange(1)}
123 aria-label={__("First page", "betterdocs")}
124 ><FirstIcon /></button>
125 <button
126 type="button"
127 className="bd-dc-page-btn"
128 disabled={page === 1}
129 onClick={() => onPageChange(page - 1)}
130 aria-label={__("Previous page", "betterdocs")}
131 ><PrevIcon /></button>
132
133 {pageList.map((item, idx) =>
134 item === "…" ? (
135 <span key={`ellipsis-${idx}`} className="bd-dc-page-ellipsis">…</span>
136 ) : (
137 <button
138 key={item}
139 type="button"
140 className={`bd-dc-page-btn bd-dc-page-btn--num${item === page ? " bd-dc-page-btn--active" : ""}`}
141 onClick={() => item !== page && onPageChange(item)}
142 aria-label={`${__("Page", "betterdocs")} ${item}`}
143 aria-current={item === page ? "page" : undefined}
144 >{item}</button>
145 )
146 )}
147
148 <button
149 type="button"
150 className="bd-dc-page-btn"
151 disabled={page >= totalPages}
152 onClick={() => onPageChange(page + 1)}
153 aria-label={__("Next page", "betterdocs")}
154 ><NextIcon /></button>
155 <button
156 type="button"
157 className="bd-dc-page-btn"
158 disabled={page >= totalPages}
159 onClick={() => onPageChange(totalPages)}
160 aria-label={__("Last page", "betterdocs")}
161 ><LastIcon /></button>
162 </div>}
163 </div>
164 );
165 };
166
167 export default Pagination;
168