PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.1.0
AI Builder – Generate pages, blocks, images & translate with AI v2.1.0
2.7.10 2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 All 122 releases
ai-builder / assets / js / src / editor-blocks / carousel / index.js

index.js in AI Builder – Generate pages, blocks, images & translate with AI 2.1.0, at assets/js/src/editor-blocks/carousel/index.js

287 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 const { registerBlockType } = wp.blocks;
2 const { __ } = wp.i18n;
3 const { useState, useEffect } = wp.element;
4 const {
5 PanelBody,
6 TextControl,
7 ToggleControl,
8 RangeControl,
9 Button,
10 SelectControl,
11 } = wp.components;
12 const {
13 InspectorControls,
14 useBlockProps,
15 RichText,
16 MediaUpload,
17 MediaUploadCheck,
18 } = wp.blockEditor || wp.editor;
19 import "./style.css";
20
21 registerBlockType("ai-builder/aibui-carousel", {
22 title: __("Carousel (AI Builder)", "ai-builder"),
23 icon: "images-alt2",
24 category: "widgets",
25 supports: { align: ["wide", "full"], html: false },
26 attributes: {
27 slides: { type: "array", default: [] }, // [{url, alt, caption}]
28 showArrows: { type: "boolean", default: true },
29 showDots: { type: "boolean", default: true },
30 autoplay: { type: "boolean", default: true },
31 autoplayDelay: { type: "number", default: 4 }, // seconds
32 height: { type: "string", default: "420px" },
33 fit: { type: "string", default: "cover" },
34 },
35 edit: (props) => {
36 const { attributes, setAttributes } = props;
37 const {
38 slides,
39 showArrows,
40 showDots,
41 autoplay,
42 autoplayDelay,
43 height,
44 fit,
45 } = attributes;
46 const blockProps = useBlockProps({ className: "aibui-carousel" });
47 const [index, setIndex] = useState(0);
48 useEffect(() => {
49 setIndex(0);
50 }, [slides.length]);
51 useEffect(() => {
52 if (!autoplay || slides.length <= 1) return;
53 const t = setInterval(
54 () => setIndex((i) => (i + 1) % slides.length),
55 autoplayDelay * 1000
56 );
57 return () => clearInterval(t);
58 }, [autoplay, autoplayDelay, slides.length]);
59 const go = (n) => setIndex((n + slides.length) % slides.length);
60
61 const addSlide = (media) => {
62 const items = Array.isArray(media) ? media : [media];
63 const appended = items
64 .filter((m) => m && m.url)
65 .map((m) => ({ url: m.url, alt: m.alt || "", caption: "" }));
66 if (appended.length) setAttributes({ slides: [...slides, ...appended] });
67 };
68
69 const updateSlide = (idx, patch) => {
70 const next = slides.map((s, i) => (i === idx ? { ...s, ...patch } : s));
71 setAttributes({ slides: next });
72 };
73
74 const removeSlide = (idx) => {
75 const next = slides.filter((_, i) => i !== idx);
76 setAttributes({ slides: next });
77 };
78
79 return (
80 <div {...blockProps}>
81 <InspectorControls>
82 <PanelBody title={__("Settings", "ai-builder")} initialOpen>
83 <ToggleControl
84 label={__("Show arrows", "ai-builder")}
85 checked={showArrows}
86 onChange={(v) => setAttributes({ showArrows: v })}
87 />
88 <ToggleControl
89 label={__("Show dots", "ai-builder")}
90 checked={showDots}
91 onChange={(v) => setAttributes({ showDots: v })}
92 />
93 <ToggleControl
94 label={__("Autoplay", "ai-builder")}
95 checked={autoplay}
96 onChange={(v) => setAttributes({ autoplay: v })}
97 />
98 {autoplay && (
99 <RangeControl
100 label={__("Autoplay delay (s)", "ai-builder")}
101 min={2}
102 max={12}
103 value={autoplayDelay}
104 onChange={(v) => setAttributes({ autoplayDelay: v })}
105 />
106 )}
107 <TextControl
108 label={__("Height", "ai-builder")}
109 value={height}
110 onChange={(v) => setAttributes({ height: v })}
111 help={__("Any CSS size (e.g. 420px, 50vh)", "ai-builder")}
112 />
113 <SelectControl
114 label={__("Image fit", "ai-builder")}
115 value={fit}
116 options={[
117 { label: "Cover", value: "cover" },
118 { label: "Contain", value: "contain" },
119 ]}
120 onChange={(v) => setAttributes({ fit: v })}
121 />
122 <MediaUploadCheck>
123 <MediaUpload
124 multiple
125 gallery
126 onSelect={addSlide}
127 allowedTypes={["image"]}
128 render={({ open }) => (
129 <Button isPrimary onClick={open} style={{ marginTop: 8 }}>
130 {__("Add slides", "ai-builder")}
131 </Button>
132 )}
133 />
134 </MediaUploadCheck>
135 </PanelBody>
136 </InspectorControls>
137
138 <div className="aibui-carousel-preview" style={{ height }}>
139 <div
140 className="aibui-carousel-track"
141 style={{ transform: `translateX(-${index * 100}%)` }}
142 >
143 {slides.map((s, i) => (
144 <div key={i} className="aibui-carousel-slide">
145 {s.url ? (
146 <img
147 src={s.url}
148 alt={s.alt || ""}
149 style={{ objectFit: fit }}
150 />
151 ) : (
152 <div className="aibui-carousel-empty">
153 {__("Select an image", "ai-builder")}
154 </div>
155 )}
156 <RichText
157 tagName="div"
158 className="aibui-carousel-caption"
159 value={s.caption}
160 placeholder={__("Caption (optional)", "ai-builder")}
161 onChange={(val) => updateSlide(i, { caption: val })}
162 />
163 <button
164 type="button"
165 className="aibui-carousel-remove"
166 onClick={() => removeSlide(i)}
167 >
168 &times;
169 </button>
170 </div>
171 ))}
172 </div>
173 <div className="aibui-carousel-controls">
174 {showArrows && (
175 <>
176 <button
177 className="aibui-carousel-arrow aibui-carousel-prev"
178 aria-label={__("Previous", "ai-builder")}
179 onClick={() => go(index - 1)}
180 >
181 &lsaquo;
182 </button>
183 <button
184 className="aibui-carousel-arrow aibui-carousel-next"
185 aria-label={__("Next", "ai-builder")}
186 onClick={() => go(index + 1)}
187 >
188 &rsaquo;
189 </button>
190 </>
191 )}
192 {showDots && (
193 <div className="aibui-carousel-dots">
194 {slides.map((_, i) => (
195 <span
196 key={i}
197 className={`aibui-carousel-dot${
198 i === index ? " is-active" : ""
199 }`}
200 onClick={() => go(i)}
201 />
202 ))}
203 </div>
204 )}
205 </div>
206 </div>
207
208 {/* actions moved into panel */}
209 </div>
210 );
211 },
212 save: (props) => {
213 const { attributes } = props;
214 const {
215 slides,
216 showArrows,
217 showDots,
218 autoplay,
219 autoplayDelay,
220 height,
221 fit,
222 } = attributes;
223 const blockProps = wp.blockEditor
224 ? wp.blockEditor.useBlockProps.save({ className: "aibui-carousel" })
225 : {};
226 return (
227 <div
228 {...blockProps}
229 data-autoplay={autoplay ? "1" : "0"}
230 data-delay={autoplayDelay}
231 >
232 <div className="aibui-carousel-preview" style={{ height }}>
233 <div className="aibui-carousel-track">
234 {slides.map((s, i) => (
235 <div key={i} className="aibui-carousel-slide">
236 {s.url ? (
237 <img
238 src={s.url}
239 alt={s.alt || ""}
240 style={{ objectFit: fit || "cover" }}
241 />
242 ) : null}
243 {s.caption ? (
244 <div
245 className="aibui-carousel-caption"
246 dangerouslySetInnerHTML={{ __html: s.caption }}
247 />
248 ) : null}
249 </div>
250 ))}
251 </div>
252 <div className="aibui-carousel-controls">
253 {showArrows ? (
254 <>
255 <button
256 className="aibui-carousel-arrow aibui-carousel-prev"
257 aria-label="Previous"
258 >
259 &lsaquo;
260 </button>
261 <button
262 className="aibui-carousel-arrow aibui-carousel-next"
263 aria-label="Next"
264 >
265 &rsaquo;
266 </button>
267 </>
268 ) : null}
269 {showDots ? (
270 <div className="aibui-carousel-dots">
271 {slides.map((_, i) => (
272 <span
273 key={i}
274 className={`aibui-carousel-dot${
275 i === 0 ? " is-active" : ""
276 }`}
277 />
278 ))}
279 </div>
280 ) : null}
281 </div>
282 </div>
283 </div>
284 );
285 },
286 });
287