PluginProbe
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News / 4.0.2
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News v4.0.2
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 2.3.5 2.3.6 2.4.0 2.4.1 2.4.10 2.4.11 2.4.12 2.4.13 2.4.14 2.4.15 2.4.16 2.4.17 2.4.18 2.4.19 2.4.2 2.4.20 2.4.21 All 88 releases
post-carousel / src / prebuild-library / readyPatterns.js

readyPatterns.js in Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News 4.0.2, at src/prebuild-library/readyPatterns.js

451 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useCallback, useMemo, memo, useState, useEffect } from "@wordpress/element";
2 import { FilterPart, HeaderWithFilter } from "./filter/filterPart";
3 import { __ } from "@wordpress/i18n";
4 import { HeartIcon, HeartIconFull, ImportIcon, PreviewIcon, ProBadgeIcon, RotateIcon } from "./icons";
5 import { PATTERN_CATEGORIES, DEFAULTS, KEYBOARD_KEYS, API_ENDPOINTS } from "./constants";
6 import { decodeEntities } from "@wordpress/html-entities";
7 import FilterSidebar from "./filter/sidebarFilter";
8
9 // Pattern categories moved to constants.js
10 const Skeleton = (props) => {
11 const { type, size, loop, unit, c_s, classes } = props;
12
13 const getSize = () => {
14 let css = {};
15 switch (type) {
16 case "image":
17 case "circle":
18 css = { width: size ? size + "px" : "300px", height: size ? size + "px" : "300px" };
19 break;
20 case "title":
21 css = { width: `${size ? size : "100"}${unit ? unit : "%"}` };
22 break;
23 case "button":
24 css = { width: size ? size + "px" : "90px" };
25 break;
26 case "custom_size":
27 css = {
28 width: `${c_s.size1 ? c_s.size1 : "100"}${c_s.unit1 ? c_s.unit1 : "%"}`,
29 height: `${c_s.size2 ? c_s.size2 : "20"}${c_s.unit2 ? c_s.unit2 : "px"}`,
30 borderRadius: c_s.br ? c_s.br + "px" : "0px",
31 };
32 break;
33 default:
34 break;
35 }
36 return css;
37 };
38 return (
39 <>
40 {loop ? (
41 <>
42 {Array(parseInt(loop))
43 .fill("1")
44 .map((x, i) => {
45 return (
46 <div
47 key={i}
48 className={`sp_smart_skeleton__${type} sp_smart_frequency loop ${classes ? classes : ""}`}
49 style={getSize()}
50 ></div>
51 );
52 })}
53 </>
54 ) : (
55 <div
56 className={`sp_smart_skeleton__${type} sp_smart_frequency ${classes ? classes : ""}`}
57 style={getSize()}
58 ></div>
59 )}
60 </>
61 );
62 };
63
64 const Tooltip = ({text}) => {
65 return (
66 <div className="sp-smart-import-or-pro-tooltip">
67 <span>{text}</span>
68 </div>
69 )
70 }
71
72 // Individual Card Component.
73 export const PremadeCard = memo(({ data, localizedData, wishListArr, setWListAction, reload, reloadId, onImport }) => {
74 const isInWishlist = wishListArr?.includes(String(data.ID));
75 const isLoading = reload && reloadId === data.ID;
76 const isPro = data?.pro;
77 const handleWishlistClick = useCallback(() => {
78 setWListAction(data.ID, isInWishlist ? "remove" : "");
79 }, [data.ID, isInWishlist, setWListAction]);
80
81 const handleImportClick = useCallback(() => {
82 if(data?.pages?.length > 0) {
83 return onImport(data.pages);
84 }
85 if (!isPro) {
86 onImport(data.ID, data.pro);
87 }
88 }, [data.ID, data.pro, isPro, onImport]);
89
90 return (
91 <div className="sp-smart-pattern-card-body">
92 <div className="sp-smart-item-list">
93 <div className="sp-smart-item-list-overlay">
94 <a target="_blank" rel="noopener noreferrer" href={`https://wpsmartpost.com/patterns/#demo${data?.ID}`} className="sp-smart-pattern-img">
95 <img src={data.image} loading="lazy" alt={data.name} />
96 </a>
97 <a target="_blank" rel="noopener noreferrer" href={`https://wpsmartpost.com/patterns/#demo${data?.ID}`}>
98 <div className="sp-smart-pattern-overlay">
99 <a
100 className="sp-smart-overlay-view"
101 href={`https://wpsmartpost.com/patterns/#demo${data?.ID}`}
102 target="_blank"
103 rel="noopener noreferrer"
104 aria-label={__("Preview", "post-carousel") + " " + data.name}
105 >
106 <PreviewIcon />
107 </a>
108 </div>
109 </a>
110 </div>
111
112 <div className="sp-smart-item-list-info">
113 <div className="sp-smart-item-info">
114 <a
115 href={`https://wpsmartpost.com/patterns/#demo${data?.ID}`}
116 target="_blank"
117 rel="noopener noreferrer"
118 aria-label={__("Preview", "post-carousel") + " " + data.name}
119 ><span>{decodeEntities(data?.name || "")}</span></a>
120 </div>
121
122 <span className="smart-post-action-btn">
123 <span
124 className="sp-smart-pattern-wishlist"
125 onClick={handleWishlistClick}
126 role="button"
127 tabIndex={0}
128 onKeyPress={(e) => {
129 if (e.key === KEYBOARD_KEYS.ENTER || e.key === KEYBOARD_KEYS.SPACE) {
130 handleWishlistClick();
131 }
132 }}
133 aria-label={
134 isInWishlist
135 ? __("Remove from wishlist", "post-carousel")
136 : __("Add to wishlist", "post-carousel")
137 }
138 >
139 {isInWishlist ? <HeartIconFull /> : <HeartIcon />}
140 </span>
141
142 {isPro ? (
143 <a
144 className="sp-smart-btn-pro"
145 target="_blank"
146 rel="noopener noreferrer"
147 href={API_ENDPOINTS.UPGRADE_URL}
148 aria-label={__("Upgrade to Pro to access this pattern", "post-carousel")}
149 >
150 <ProBadgeIcon /> {__("Pro", "post-carousel")}
151 <Tooltip
152 text={__("Upgrade to Pro", "post-carousel")}
153 />
154 </a>
155 ) : (
156 <span
157 onClick={handleImportClick}
158 className="sp-smart-import-btn"
159 role="button"
160 tabIndex={0}
161 onKeyPress={(e) => {
162 if (e.key === KEYBOARD_KEYS.ENTER || e.key === KEYBOARD_KEYS.SPACE) {
163 handleImportClick();
164 }
165 }}
166 aria-label={__("Import", "post-carousel") + " " + data.name}
167 style={isLoading ? { pointerEvents: "none", opacity: 0.6 } : {}}
168 >
169 {isLoading ? (
170 <i className=" sp-rotate">
171 <RotateIcon />
172 </i>
173 ) : (
174 <>
175 <ImportIcon /> {__("Import", "post-carousel")}
176 <Tooltip
177 text={__("Free Pattern", "post-carousel")}
178 />
179 </>
180 )}
181 </span>
182 )}
183 </span>
184 </div>
185 </div>
186 </div>
187 );
188 });
189
190 // Loading Skeleton Component.
191 export const LoadingSkeleton = memo(() => (
192 <div className="sp-smart-pattern-grid sp-smart-pattern-col3 skeletonOverflow">
193 {Array(25)
194 .fill(null)
195 .map((_, i) => (
196 <div key={i} className="sp-smart-item-list">
197 <div className="sp-smart-item-list-overlay">
198 <Skeleton type="custom_size" c_s={{ size1: 100, unit1: "%", size2: 400, unit2: "px" }} />
199 </div>
200 <div className="sp-smart-item-list-info">
201 <Skeleton type="custom_size" c_s={{ size1: 50, unit1: "%", size2: 25, unit2: "px", br: 2 }} />
202 <span className="smart-post-action-btn">
203 <span className="sp-smart-pattern-wishlist">
204 <Skeleton
205 type="custom_size"
206 c_s={{ size1: 30, unit1: "px", size2: 25, unit2: "px", br: 2 }}
207 />
208 </span>
209 <Skeleton
210 type="custom_size"
211 c_s={{ size1: 70, unit1: "px", size2: 25, unit2: "px", br: 2 }}
212 />
213 </span>
214 </div>
215 </div>
216 ))}
217 </div>
218 ));
219
220 // Empty State Component.
221 export const EmptyState = memo(() => (
222 <span className="sp-smart-image-rotate">{__("No Data found...", "post-carousel")}</span>
223 ));
224
225 // Main Grid Component.
226 export const PremadeGrid = memo(
227 ({
228 premadeLists,
229 column,
230 searchQuery,
231 freePro,
232 showWishList,
233 wishListArr,
234 localizedData,
235 setWListAction,
236 reload,
237 reloadId,
238 onImport,
239 loading,
240 }) => {
241 // Memoize filter function
242 const filteredLists = useMemo(() => {
243 return premadeLists.filter((data) => {
244 const searchMatch = !searchQuery || data.name?.toLowerCase().includes(searchQuery.toLowerCase());
245
246 const freeProMatch =
247 freePro === "all" || (freePro === "pro" && data.pro) || (freePro === "free" && !data.pro);
248 const wishListMatch = !showWishList || wishListArr?.includes(String(data.ID));
249 return searchMatch && freeProMatch && wishListMatch;
250 });
251 }, [premadeLists, searchQuery, freePro, showWishList, wishListArr]);
252
253 // Loading state.
254 if (loading) {
255 return <LoadingSkeleton />;
256 }
257
258 // Empty state
259 if (filteredLists.length === 0) {
260 return <EmptyState />;
261 }
262
263 // Grid with data
264 return (
265 <div className={`sp-smart-pattern-grid sp-smart-pattern-col${column}`}>
266 {filteredLists.map((data) => (
267 <PremadeCard
268 key={data.ID}
269 data={data}
270 localizedData={localizedData}
271 wishListArr={wishListArr}
272 setWListAction={setWListAction}
273 reload={reload}
274 reloadId={reloadId}
275 onImport={onImport}
276 />
277 ))}
278 </div>
279 );
280 }
281 );
282
283 // Main StarterSites Component
284 const ReadyPatterns = (props) => {
285 const {
286 filterValue,
287 state,
288 setState,
289 // useState,
290 // useEffect,
291 // useRef,
292 _changeVal,
293 filterByCategoryKey,
294 setWListAction,
295 wishListArr,
296 _fetchFile,
297 onClose,
298 currentBlockName,
299 isSingleBlock,
300 } = props;
301
302 const { current, designs, reload, reloadId, fetching, loading } = state;
303 const localizedData = sp_smart_post_block_localize || {};
304 // Local state management.
305 const [column, setColumn] = useState(DEFAULTS.COLUMN);
306 const [searchQuery, setSearchQuery] = useState(DEFAULTS.SEARCH_QUERY);
307 const [trend, setTrend] = useState(DEFAULTS.TREND);
308 const [freePro, setFreePro] = useState(DEFAULTS.FREE_PRO);
309 const [showWishList, setShowWishList] = useState(false);
310
311 // Memoize sorted lists.
312 const premadeLists = useMemo(() => {
313 if (!current.length) return [];
314
315 const lists = [...current];
316
317 if (trend === "latest" || trend === "all") {
318 return lists.sort((a, b) => b.ID - a.ID);
319 }
320
321 if (trend === "popular" && lists[0]?.hit !== undefined) {
322 return lists.sort((a, b) => b.hit - a.hit);
323 }
324
325 return lists;
326 }, [current, trend]);
327
328 // Handle filter changes.
329 const handleFilterChange = useCallback(
330 (type) => {
331 const filteredData = type === "all" ? designs : filterByCategoryKey(designs, type);
332 setState((prev) => ({
333 ...prev,
334 designFilter: type,
335 current: filteredData,
336 }));
337 },
338 [designs, filterByCategoryKey, setState]
339 );
340
341 // Unified state change handler.
342 const changeStates = useCallback(
343 (type, value) => {
344 const handlers = {
345 freePro: () => setFreePro(value),
346 search: () => setSearchQuery(value),
347 column: () => setColumn(value),
348 wishlist: () => setShowWishList(value),
349 trend: () => setTrend(value),
350 filter: () => handleFilterChange(value),
351 };
352
353 handlers[type]?.();
354 },
355 [handleFilterChange]
356 );
357
358 // Memoize import handler.
359 const handleImport = useCallback(
360 (templateID, isPro) => {
361 _changeVal(templateID, isPro);
362 },
363 [_changeVal]
364 );
365
366 return (
367 <>
368 {isSingleBlock && (
369 <HeaderWithFilter
370 changeStates={changeStates}
371 useState={useState}
372 onClose={onClose}
373 currentBlockName={currentBlockName}
374 column={column}
375 showWishList={showWishList}
376 searchQuery={searchQuery}
377 fetching={fetching}
378 _fetchFile={_fetchFile}
379 fields={{ filter: true, trend: true, freePro: true }}
380 fieldOptions={{
381 filterArr: PATTERN_CATEGORIES,
382 trendArr: [],
383 freeProArr: [],
384 }}
385 fieldValue={{ filter: filterValue, trend, freePro }}
386 wishListArr={wishListArr}
387 />
388 )}
389 <div className="sp-smart-template-wrap sp-smart-pattern">
390 <div className="sp-smart-template-library-sidebar-wrapper">
391 {/* <h3 className="sp-smart-template-library-sidebar-header">
392 {__("Ready Patterns", "post-carousel")}
393 </h3> */}
394 <div className="sp-smart-template-library-sidebar-content">
395 <FilterSidebar
396 changeStates={changeStates}
397 fieldOptions={{
398 filterArr: PATTERN_CATEGORIES,
399 trendArr: [],
400 freeProArr: [],
401 }}
402 fields={{ filter: true, trend: true, freePro: true }}
403 fieldValue={{ filter: filterValue, trend, freePro }}
404 allData={ state?.designs }
405 tabName={ "ready-patterns" }
406 />
407 </div>
408 </div>
409 <div className="sp-smart-pattern-list-container sp-smart-pattern-card">
410 {!isSingleBlock && (
411 <FilterPart
412 changeStates={changeStates}
413 useState={useState}
414 column={column}
415 showWishList={showWishList}
416 searchQuery={searchQuery}
417 fetching={fetching}
418 _fetchFile={_fetchFile}
419 fields={{ filter: true, trend: true, freePro: true }}
420 fieldOptions={{
421 filterArr: PATTERN_CATEGORIES,
422 trendArr: [],
423 freeProArr: [],
424 }}
425 fieldValue={{ filter: filterValue, trend, freePro }}
426 wishListArr={wishListArr}
427 designLibrary={ true }
428 />
429 )}
430
431 <PremadeGrid
432 premadeLists={premadeLists}
433 column={column}
434 searchQuery={searchQuery}
435 freePro={freePro}
436 showWishList={showWishList}
437 wishListArr={wishListArr}
438 localizedData={localizedData}
439 setWListAction={setWListAction}
440 reload={reload}
441 reloadId={reloadId}
442 onImport={handleImport}
443 loading={loading}
444 />
445 </div>
446 </div>
447 </>
448 );
449 };
450
451 export default ReadyPatterns;