PluginProbe ʕ •ᴥ•ʔ
Presto Player / 4.3.2
Presto Player v4.3.2
4.3.2 4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / src / admin / dashboard / components / RankedTable.js
presto-player / src / admin / dashboard / components Last commit date
Emails 1 month ago EngagementChart 2 months ago MediaHub 1 month ago Onboarding 2 months ago Popup 2 months ago Skeletons 1 month ago WhatsNew 2 months ago charts 2 months ago test 2 months ago AdminMenuSync.js 2 months ago ChartEmptyState.js 2 months ago ChooseDate.js 2 months ago ColorPicker.js 2 months ago ExtendPlugins.js 2 months ago Filters.js 2 months ago Link.js 2 months ago Navbar.js 1 month ago NoFound.js 2 months ago PageHeader.js 2 months ago PluginRecommendations.js 2 months ago PostScheduleField.js 2 months ago PrestoPlayerIcon.js 2 months ago ProGateOverlay.js 2 months ago QuickAccess.js 2 months ago RankedTable.js 2 months ago StatCard.js 2 months ago TopMedia.js 2 months ago TopPerformingMedia.js 2 months ago TopUsers.js 2 months ago TruncatedTitle.js 2 months ago UpgradeNotice.js 2 months ago UpgradeToPro.js 2 months ago VideoModal.js 2 months ago WelcomeBanner.js 2 months ago
RankedTable.js
225 lines
1 const { __ } = wp.i18n;
2 import { Container, Table, Pagination, Text } from "@bsf/force-ui";
3
4 /**
5 * Build the page list for the pager — first/last pinned, current ± siblings,
6 * ellipses for gaps. Bounded output keeps the pager inside the card on every
7 * page so the last page stays reachable however many pages there are.
8 *
9 * Returns at most `boundary*2 + siblings*2 + 3` entries (default = 7).
10 */
11 const buildPagerWindow = (currentPage, totalPages, siblings = 1, boundary = 1) => {
12 const totalSlots = boundary * 2 + siblings * 2 + 3;
13 if (totalPages <= totalSlots) {
14 return Array.from({ length: totalPages }, (_, i) => i + 1);
15 }
16
17 const leftSibling = Math.max(currentPage - siblings, boundary + 1);
18 const rightSibling = Math.min(currentPage + siblings, totalPages - boundary);
19 const showLeftEllipsis = leftSibling > boundary + 2;
20 const showRightEllipsis = rightSibling < totalPages - boundary - 1;
21
22 const range = (start, end) =>
23 Array.from({ length: end - start + 1 }, (_, i) => start + i);
24
25 const head = range(1, boundary);
26 const tail = range(totalPages - boundary + 1, totalPages);
27
28 if (!showLeftEllipsis && showRightEllipsis) {
29 return [...range(1, boundary + siblings * 2 + 2), "ellipsis-right", ...tail];
30 }
31 if (showLeftEllipsis && !showRightEllipsis) {
32 return [
33 ...head,
34 "ellipsis-left",
35 ...range(totalPages - boundary - siblings * 2 - 1, totalPages),
36 ];
37 }
38 return [
39 ...head,
40 "ellipsis-left",
41 ...range(leftSibling, rightSibling),
42 "ellipsis-right",
43 ...tail,
44 ];
45 };
46
47 /**
48 * Card + table + pagination chrome shared by Analytics ranked-list widgets
49 * (Top Media, Top Users, etc.).
50 *
51 * The shared component owns ONLY the chrome: card wrapper, header, table
52 * layout, and the pagination footer ("X–Y of N items" + numbered page
53 * buttons). Per-row content is supplied by `columns`; an optional trailing
54 * action cell is supplied by `rowAction`. Pagination is fully controlled
55 * — pass the metadata + `onPageChange` callback from the parent's data
56 * hook (server-side pagination) and the footer wires itself up.
57 *
58 * @param {Object} props
59 * @param {string} props.title Card header text.
60 * @param {Array} props.columns Column definitions:
61 * { key, header, headerClassName?, cellClassName?, render(row) }
62 * @param {Array} props.rows Current page of rows.
63 * @param {Function} [props.rowKey] (row) => key. Falls back to row.id.
64 * @param {Function} [props.onRowClick] (row) => void. Makes the row clickable.
65 * @param {Function} [props.rowAction] (row) => ReactNode. Renders trailing action cell.
66 * @param {Object} [props.pagination] { currentPage, totalItems, totalPages, perPage, onPageChange }.
67 * When omitted, no footer renders.
68 * @param {ReactNode} [props.emptyState] Rendered when `rows` is empty.
69 * @param {string} [props.className] Extra classes applied to the outer card Container.
70 */
71 const RankedTable = ({
72 title,
73 columns = [],
74 rows = [],
75 rowKey,
76 onRowClick,
77 rowAction,
78 pagination,
79 emptyState = null,
80 className = "",
81 }) => {
82 const hasPagination = Boolean(pagination);
83 const total = pagination?.totalItems ?? rows.length;
84 const totalPages = pagination?.totalPages ?? 0;
85 const currentPage = pagination?.currentPage ?? 1;
86 const perPage = pagination?.perPage ?? rows.length;
87 const startIndex = hasPagination ? (currentPage - 1) * perPage : 0;
88 const endIndex = hasPagination
89 ? Math.min(startIndex + rows.length, total)
90 : rows.length;
91
92 const goToPage = (page) => {
93 pagination?.onPageChange?.(page);
94 };
95
96 const renderPaginationItems = () => {
97 if (totalPages <= 1) return null;
98
99 const items = buildPagerWindow(currentPage, totalPages).map((entry) =>
100 typeof entry === "number" ? (
101 <Pagination.Item
102 key={entry}
103 isActive={entry === currentPage}
104 onClick={() => goToPage(entry)}
105 >
106 {entry}
107 </Pagination.Item>
108 ) : (
109 <Pagination.Ellipsis key={entry} />
110 )
111 );
112
113 return (
114 <>
115 <Pagination.Previous
116 onClick={() => goToPage(Math.max(currentPage - 1, 1))}
117 disabled={currentPage === 1}
118 />
119 {items}
120 <Pagination.Next
121 onClick={() => goToPage(Math.min(currentPage + 1, totalPages))}
122 disabled={currentPage === totalPages}
123 />
124 </>
125 );
126 };
127
128 const cardClasses = [
129 "overflow-hidden border-0.5 border-solid border-border-subtle rounded-xl shadow-sm bg-background-primary",
130 "h-full w-full",
131 className,
132 ]
133 .filter(Boolean)
134 .join(" ");
135
136 const getKey = (row, idx) => {
137 if (rowKey) return rowKey(row);
138 return row?.id ?? idx;
139 };
140
141 return (
142 <Container direction="column" gap="none" className={cardClasses}>
143 <Container align="center" gap="xs" className="px-3 py-3">
144 <Text className="text-base font-semibold text-text-primary">
145 {title}
146 </Text>
147 </Container>
148
149 {rows.length === 0 ? (
150 emptyState
151 ) : (
152 <>
153 <div className="[&>div]:rounded-none [&>div]:border-b-0">
154 <Table className="shadow-none border-0 rounded-none">
155 <Table.Head className="bg-background-primary border-b-0.5">
156 {columns.map((col, idx) => (
157 <Table.HeadCell
158 key={col.key ?? idx}
159 className={col.headerClassName}
160 >
161 {col.header}
162 </Table.HeadCell>
163 ))}
164 {rowAction ? <Table.HeadCell className="py-2" /> : null}
165 </Table.Head>
166
167 <Table.Body>
168 {rows.map((row, idx) => (
169 <Table.Row
170 key={getKey(row, idx)}
171 className={
172 onRowClick
173 ? "cursor-pointer hover:bg-background-secondary"
174 : undefined
175 }
176 onClick={onRowClick ? () => onRowClick(row) : undefined}
177 >
178 {columns.map((col, colIdx) => (
179 <Table.Cell
180 key={col.key ?? colIdx}
181 className={col.cellClassName}
182 >
183 {col.render(row)}
184 </Table.Cell>
185 ))}
186 {rowAction ? (
187 <Table.Cell className="whitespace-nowrap py-2">
188 {rowAction(row)}
189 </Table.Cell>
190 ) : null}
191 </Table.Row>
192 ))}
193 </Table.Body>
194 </Table>
195 </div>
196
197 {hasPagination && totalPages > 1 ? (
198 <div className="bg-background-primary px-4 py-3 border-t-[0.5px] border-x-0 border-b-0 border-solid border-border-subtle">
199 <Container align="center" justify="between" className="w-full">
200 <Text
201 size="sm"
202 weight="regular"
203 className="text-text-secondary"
204 >
205 {`${startIndex + 1}${endIndex} ${__(
206 "of",
207 "presto-player"
208 )} ${total} ${__("items", "presto-player")}`}
209 </Text>
210 <Pagination className="w-fit [&_li]:!my-0">
211 <Pagination.Content>
212 {renderPaginationItems()}
213 </Pagination.Content>
214 </Pagination>
215 </Container>
216 </div>
217 ) : null}
218 </>
219 )}
220 </Container>
221 );
222 };
223
224 export default RankedTable;
225