PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 3.16.2
GiveWP – Donation Plugin and Fundraising Platform v3.16.2
4.16.4 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 3 years ago index.tsx 1 year ago
index.tsx
267 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
11 export interface ListTableProps {
12 //required
13 apiSettings: {table: {columns: Array<ListTableColumn>; id: string}};
14 title: string;
15 data: {items: Array<{}>};
16 setSortDirectionForColumn: (event: React.MouseEvent<HTMLElement>, column: string) => void;
17 sortField: {sortColumn: string; sortDirection: string};
18
19 //optional
20 pluralName?: string;
21 singleName?: string;
22 rowActions?: (({item, data, addRow, removeRow}) => JSX.Element) | JSX.Element | JSX.Element[] | Function | null;
23 parameters?: {};
24 error?: {} | Boolean;
25 isLoading?: Boolean;
26 align?: 'start' | 'center' | 'end';
27 testMode?: boolean;
28 listTableBlankSlate: JSX.Element;
29 productRecommendation?: JSX.Element;
30 columnFilters?: Array<ColumnFilterConfig>;
31 }
32
33 export interface ListTableColumn {
34 //required
35 id: string;
36 sortable: boolean;
37 visible: boolean;
38 label: string;
39 }
40
41 /**
42 * Updated to replace the static message when no results are found with the blank slate design.
43 * @since 2.27.0
44 */
45 export const ListTable = ({
46 singleName = __('item', 'give'),
47 pluralName = __('items', 'give'),
48 title,
49 data,
50 rowActions = null,
51 parameters = {},
52 error = false,
53 isLoading = false,
54 align = 'start',
55 apiSettings,
56 setSortDirectionForColumn,
57 sortField,
58 testMode,
59 listTableBlankSlate,
60 productRecommendation,
61 columnFilters = [],
62 }: ListTableProps) => {
63 const [updateErrors, setUpdateErrors] = useState<{errors: Array<number>; successes: Array<number>}>({
64 errors: [],
65 successes: [],
66 });
67 const [errorOverlay, setErrorOverlay] = useState<string | boolean>(false);
68 const [initialLoad, setInitialLoad] = useState<boolean>(true);
69 const [loadingOverlay, setLoadingOverlay] = useState<string | boolean>(false);
70 const [overlayWidth, setOverlayWidth] = useState(0);
71 const tableRef = useRef<null | HTMLTableElement>();
72 const isEmpty = !error && data?.items.length === 0;
73
74 useEffect(() => {
75 initialLoad && data && setInitialLoad(false);
76 }, [data]);
77
78 useEffect(() => {
79 if (isLoading) {
80 // we need to set the overlay width in JS because tables only respect 'position: relative' in FireFox
81 if (tableRef.current) {
82 setOverlayWidth(tableRef.current.getBoundingClientRect().width);
83 }
84 setLoadingOverlay(styles.appear);
85 }
86 if (!isLoading && loadingOverlay) {
87 setLoadingOverlay(styles.disappear);
88 const timeoutId = setTimeout(() => setLoadingOverlay(false), 100);
89 return () => clearTimeout(timeoutId);
90 }
91 }, [isLoading]);
92
93 useEffect(() => {
94 let timeoutId;
95 if (updateErrors.errors.length) {
96 setErrorOverlay(styles.appear);
97 timeoutId = setTimeout(
98 () =>
99 document.getElementById(styles.updateError).scrollIntoView?.({behavior: 'smooth', block: 'center'}),
100 100
101 );
102 } else if (errorOverlay) {
103 setErrorOverlay(styles.disappear);
104 timeoutId = setTimeout(() => setErrorOverlay(false), 100);
105 }
106 return () => clearTimeout(timeoutId);
107 }, [updateErrors.errors]);
108
109 const clearUpdateErrors = () => {
110 setUpdateErrors({errors: [], successes: []});
111 };
112
113 const visibleColumns = apiSettings.table.columns?.filter(
114 (column) => column.visible || column.visible === undefined
115 );
116
117 const isScrollable = () => {
118 return document.body.scrollHeight > document.body.clientHeight;
119 };
120
121 return (
122 <>
123 {initialLoad && !error ? (
124 <div className={styles.initialLoad}>
125 <div role="dialog" aria-labelledby="giveListTableLoadingMessage" className={cx(styles.tableGroup)}>
126 <Spinner size={'large'} />
127 <h2 id="giveListTableLoadingMessage">{sprintf(__('Loading %s', 'give'), pluralName)}</h2>
128 </div>
129 </div>
130 ) : (
131 <div
132 role="group"
133 aria-labelledby="giveListTableCaption"
134 aria-describedby="giveListTableMessage"
135 className={styles.tableGroup}
136 tabIndex={0}
137 >
138 {loadingOverlay && (
139 <div className={cx(styles.overlay, loadingOverlay)}>
140 <div className={cx(isScrollable() && styles.relativeContainer)}>
141 <div className={styles.fixedContent}>
142 <Spinner size={'medium'} />
143 </div>
144 </div>
145 </div>
146 )}
147 <table ref={tableRef} className={`giveListTable ${isLoading ? `giveListTableIsLoading` : `giveListTableIsLoaded`} ${styles.table}`} >
148 <caption id="giveListTableCaption" className={styles.tableCaption}>
149 {title}
150 </caption>
151 <thead className={styles[apiSettings.table.id]}>
152 <tr>
153 <th
154 scope="col"
155 aria-sort="none"
156 className={cx(styles.tableColumnHeader, styles.selectAll, {
157 [styles['testMode']]: testMode,
158 })}
159 data-column="select"
160 >
161 <BulkActionCheckboxAll pluralName={pluralName} data={data} />
162 </th>
163 <>
164 {visibleColumns?.map((column) => (
165 <th
166 scope="col"
167 aria-sort={
168 column.label === sortField.sortColumn
169 ? sortField.sortDirection === 'asc'
170 ? 'ascending'
171 : 'descending'
172 : 'none'
173 }
174 className={cx(styles.tableColumnHeader, {
175 [styles[column.id]]: true,
176 [styles['testMode']]: testMode,
177 })}
178 data-column={column.id}
179 key={column.id}
180 >
181 <ListTableHeaders
182 column={column}
183 sortField={sortField}
184 setSortDirectionForColumn={setSortDirectionForColumn}
185 />
186 </th>
187 ))}
188 </>
189 </tr>
190 </thead>
191 <tbody className={styles.tableContent}>
192 {productRecommendation}
193 <ListTableRows
194 columns={visibleColumns}
195 data={data}
196 isLoading={isLoading}
197 singleName={singleName}
198 rowActions={rowActions}
199 parameters={parameters}
200 setUpdateErrors={setUpdateErrors}
201 columnFilters={columnFilters}
202 />
203 </tbody>
204 </table>
205 {errorOverlay && (
206 <div className={cx(styles.overlay, errorOverlay)}>
207 <div id={styles.updateError} role="dialog" aria-labelledby="giveListTableErrorMessage">
208 {Boolean(updateErrors.successes.length) && (
209 <span>
210 {updateErrors.successes.length +
211 ' ' +
212 // translators:
213 // Like '1 item was updated successfully'
214 // or '3 items were updated successfully'
215 _n(
216 sprintf('%s was updated successfully.', singleName),
217 sprintf('%s were updated successfully.', pluralName),
218 updateErrors.successes.length,
219 'give'
220 )}
221 </span>
222 )}
223 <span id="giveListTableErrorMessage">
224 {updateErrors.errors.length +
225 ' ' +
226 _n(
227 `${singleName} couldn't be updated.`,
228 `${pluralName} couldn't be updated.`,
229 updateErrors.errors.length,
230 'give'
231 )}
232 </span>
233 <button
234 type="button"
235 className={cx('dashicons dashicons-dismiss', styles.dismiss)}
236 onClick={clearUpdateErrors}
237 >
238 <span className="give-visually-hidden">{__('dismiss', 'give')}</span>
239 </button>
240 </div>
241 </div>
242 )}
243 <div id="giveListTableMessage">
244 {isEmpty && (
245 <div role="status" className={styles.statusMessage}>
246 {listTableBlankSlate}
247 </div>
248 )}
249 {error && (
250 <>
251 <div role="alert" className={styles.statusMessage}>
252 {sprintf(__('There was a problem retrieving the %s.', 'give'), pluralName)}
253 </div>
254 <div className={styles.statusMessage}>
255 <a href={window.location.href.toString()}>
256 {__('Click here to reload the page.', 'give')}
257 </a>
258 </div>
259 </>
260 )}
261 </div>
262 </div>
263 )}
264 </>
265 );
266 };
267