PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.16.3
GiveWP – Donation Plugin and Fundraising Platform v4.16.3
4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / Views / Components / ListTable / ListTable / index.tsx
give / src / Views / Components / ListTable / ListTable Last commit date
ListTable.module.scss 8 months ago index.tsx 8 months ago
index.tsx
280 lines
1 import {useEffect, useRef, useState} from 'react';
2 import {__, _n, sprintf} from '@wordpress/i18n';
3 import cx from 'classnames';
4 import styles from './ListTable.module.scss';
5 import {Spinner} from '../../index';
6 import {BulkActionCheckboxAll} from '@givewp/components/ListTable/BulkActions/BulkActionCheckbox';
7 import ListTableHeaders from '@givewp/components/ListTable/ListTableHeaders';
8 import ListTableRows from '@givewp/components/ListTable/ListTableRows';
9 import {ColumnFilterConfig} from '@givewp/components/ListTable/ListTablePage';
10 import { useTriggerResize } from '../../hooks';
11
12 export interface ListTableProps {
13 //required
14 apiSettings: {table: {columns: Array<ListTableColumn>; id: string}};
15 title: string;
16 data: {items: Array<{}>};
17 setSortDirectionForColumn: (event: React.MouseEvent<HTMLElement>, column: string) => void;
18 sortField: {sortColumn: string; sortDirection: string};
19
20 //optional
21 pluralName?: string;
22 singleName?: string;
23 rowActions?: (({item, data, addRow, removeRow, listTableApi}) => JSX.Element) | JSX.Element | JSX.Element[] | Function | null;
24 parameters?: {};
25 error?: {} | Boolean;
26 isLoading?: Boolean;
27 align?: 'start' | 'center' | 'end';
28 testMode?: boolean;
29 listTableBlankSlate: JSX.Element;
30 productRecommendation?: JSX.Element;
31 columnFilters?: Array<ColumnFilterConfig>;
32 includeBulkActionsCheckbox?: boolean;
33 children?: JSX.Element | JSX.Element[] | null;
34 }
35
36 export interface ListTableColumn {
37 //required
38 id: string;
39 sortable: boolean;
40 visible: boolean;
41 label: string;
42 }
43
44 /**
45 * Updated to replace the static message when no results are found with the blank slate design.
46 * @since 2.27.0
47 */
48 export const ListTable = ({
49 singleName = __('item', 'give'),
50 pluralName = __('items', 'give'),
51 title,
52 data,
53 rowActions = null,
54 parameters = {},
55 error = false,
56 isLoading = false,
57 align = 'start',
58 apiSettings,
59 setSortDirectionForColumn,
60 sortField,
61 testMode,
62 listTableBlankSlate,
63 productRecommendation,
64 columnFilters = [],
65 includeBulkActionsCheckbox = false,
66 children = null,
67 }: ListTableProps) => {
68 const [updateErrors, setUpdateErrors] = useState<{errors: Array<number>; successes: Array<number>}>({
69 errors: [],
70 successes: [],
71 });
72 const [errorOverlay, setErrorOverlay] = useState<string | boolean>(false);
73 const [initialLoad, setInitialLoad] = useState<boolean>(true);
74 const [loadingOverlay, setLoadingOverlay] = useState<string | boolean>(false);
75 const [overlayWidth, setOverlayWidth] = useState(0);
76 const tableRef = useRef<null | HTMLTableElement>();
77 const isEmpty = !error && data?.items.length === 0;
78
79 useTriggerResize(data);
80
81 useEffect(() => {
82 initialLoad && data && setInitialLoad(false);
83 }, [data]);
84
85 useEffect(() => {
86 if (isLoading) {
87 // we need to set the overlay width in JS because tables only respect 'position: relative' in FireFox
88 if (tableRef.current) {
89 setOverlayWidth(tableRef.current.getBoundingClientRect().width);
90 }
91 setLoadingOverlay(styles.appear);
92 }
93 if (!isLoading && loadingOverlay) {
94 setLoadingOverlay(styles.disappear);
95 const timeoutId = setTimeout(() => setLoadingOverlay(false), 100);
96 return () => clearTimeout(timeoutId);
97 }
98 }, [isLoading]);
99
100 useEffect(() => {
101 let timeoutId;
102 if (updateErrors.errors.length) {
103 setErrorOverlay(styles.appear);
104 timeoutId = setTimeout(
105 () =>
106 document.getElementById(styles.updateError).scrollIntoView?.({behavior: 'smooth', block: 'center'}),
107 100
108 );
109 } else if (errorOverlay) {
110 setErrorOverlay(styles.disappear);
111 timeoutId = setTimeout(() => setErrorOverlay(false), 100);
112 }
113 return () => clearTimeout(timeoutId);
114 }, [updateErrors.errors]);
115
116 const clearUpdateErrors = () => {
117 setUpdateErrors({errors: [], successes: []});
118 };
119
120 const visibleColumns = apiSettings.table.columns?.filter(
121 (column) => column.visible || column.visible === undefined
122 );
123
124 const isScrollable = () => {
125 return document.body.scrollHeight > document.body.clientHeight;
126 };
127
128 return (
129 <>
130 {initialLoad && !error ? (
131 <div className={styles.loadingContainer}>
132 <div role="dialog" aria-labelledby="giveListTableLoadingMessage" className={styles.loadingContainerContent}>
133 <Spinner />
134 <h2 id="giveListTableLoadingMessage" className={styles.loadingContainerContentText}>{sprintf(__('Loading %s', 'give'), pluralName)}</h2>
135 </div>
136 </div>
137 ) : (
138 <div
139 role="group"
140 aria-labelledby="giveListTableCaption"
141 aria-describedby="giveListTableMessage"
142 className={styles.tableGroup}
143 tabIndex={0}
144 >
145 {loadingOverlay && (
146 <div className={cx(styles.overlay, loadingOverlay)}>
147 <div className={cx(isScrollable() && styles.relativeContainer)}>
148 <div className={styles.fixedContent}>
149 <Spinner size={'medium'} />
150 </div>
151 </div>
152 </div>
153 )}
154 <table ref={tableRef} className={`giveListTable ${isLoading ? `giveListTableIsLoading` : `giveListTableIsLoaded`} ${styles.table}`} >
155 <caption id="giveListTableCaption" className={styles.tableCaption}>
156 {title}
157 </caption>
158 <thead className={styles[apiSettings.table.id]}>
159 <tr className={styles.searchContainerRow}>{children}</tr>
160 <tr>
161 {includeBulkActionsCheckbox && (
162 <th
163 scope="col"
164 aria-sort="none"
165 className={cx(styles.tableColumnHeader, styles.selectAll, {
166 [styles['testMode']]: testMode,
167 })}
168 data-column="select"
169 >
170 <BulkActionCheckboxAll pluralName={pluralName} data={data} />
171 </th>
172 )}
173 <>
174 {visibleColumns?.map((column) => (
175 <th
176 scope="col"
177 aria-sort={
178 column.label === sortField.sortColumn
179 ? sortField.sortDirection === 'asc'
180 ? 'ascending'
181 : 'descending'
182 : 'none'
183 }
184 className={cx(styles.tableColumnHeader, {
185 [styles[column.id]]: true,
186 [styles['testMode']]: testMode,
187 })}
188 data-column={column.id}
189 key={column.id}
190 >
191 <ListTableHeaders
192 column={column}
193 sortField={sortField}
194 setSortDirectionForColumn={setSortDirectionForColumn}
195 />
196 </th>
197 ))}
198 </>
199 </tr>
200 </thead>
201 <tbody className={styles.tableContent}>
202 {productRecommendation}
203 <ListTableRows
204 apiSettings={apiSettings}
205 columns={visibleColumns}
206 data={data}
207 isLoading={isLoading}
208 tableId={apiSettings.table.id}
209 singleName={singleName}
210 rowActions={rowActions}
211 parameters={parameters}
212 setUpdateErrors={setUpdateErrors}
213 columnFilters={columnFilters}
214 includeBulkActionsCheckbox={includeBulkActionsCheckbox}
215 />
216 </tbody>
217 </table>
218 {errorOverlay && (
219 <div className={cx(styles.overlay, errorOverlay)}>
220 <div id={styles.updateError} role="dialog" aria-labelledby="giveListTableErrorMessage">
221 {Boolean(updateErrors.successes.length) && (
222 <span>
223 {updateErrors.successes.length +
224 ' ' +
225 // translators:
226 // Like '1 item was updated successfully'
227 // or '3 items were updated successfully'
228 _n(
229 sprintf('%s was updated successfully.', singleName),
230 sprintf('%s were updated successfully.', pluralName),
231 updateErrors.successes.length,
232 'give'
233 )}
234 </span>
235 )}
236 <span id="giveListTableErrorMessage">
237 {updateErrors.errors.length +
238 ' ' +
239 _n(
240 `${singleName} couldn't be updated.`,
241 `${pluralName} couldn't be updated.`,
242 updateErrors.errors.length,
243 'give'
244 )}
245 </span>
246 <button
247 type="button"
248 className={cx('dashicons dashicons-dismiss', styles.dismiss)}
249 onClick={clearUpdateErrors}
250 >
251 <span className="give-visually-hidden">{__('dismiss', 'give')}</span>
252 </button>
253 </div>
254 </div>
255 )}
256 <div id="giveListTableMessage">
257 {isEmpty && (
258 <div role="status" className={styles.statusMessage}>
259 {listTableBlankSlate}
260 </div>
261 )}
262 {error && (
263 <>
264 <div role="alert" className={styles.statusMessage}>
265 {sprintf(__('There was a problem retrieving the %s.', 'give'), pluralName)}
266 </div>
267 <div className={styles.statusMessage}>
268 <a href={window.location.href.toString()}>
269 {__('Click here to reload the page.', 'give')}
270 </a>
271 </div>
272 </>
273 )}
274 </div>
275 </div>
276 )}
277 </>
278 );
279 };
280