PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / marquee-cards / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/marquee-cards/index.js

467 lines 22.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var __ = wp.i18n.__;
4 var useState = wp.element.useState;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var useBlockProps = wp.blockEditor.useBlockProps;
8 var MediaUpload = wp.blockEditor.MediaUpload;
9 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
10 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
11 var PanelBody = wp.components.PanelBody;
12 var ToggleControl = wp.components.ToggleControl;
13 var RangeControl = wp.components.RangeControl;
14 var SelectControl = wp.components.SelectControl;
15 var TextControl = wp.components.TextControl;
16 var Button = wp.components.Button;
17
18 var _tc, _tv;
19 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
20 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
21
22 var PLACEHOLDER = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="280" height="160"><rect width="280" height="160" fill="%23e5e7eb"/><text x="50%25" y="50%25" font-family="sans-serif" font-size="13" fill="%239ca3af" text-anchor="middle" dy=".3em">No Image</text></svg>';
23
24 function cloneCard(card) {
25 return Object.assign({}, card);
26 }
27
28 /* ── Single card preview ───────────────────────────────────────────────── */
29 function CardPreview(props) {
30 var a = props.a;
31 var card = props.card;
32
33 var cardStyle = {
34 width: a.cardWidth + 'px',
35 borderRadius: a.cardRadius + 'px',
36 border: a.cardBorderWidth + 'px solid ' + a.cardBorder,
37 background: a.cardBg,
38 flexShrink: 0,
39 overflow: 'hidden',
40 boxShadow: a.cardShadow ? '0 4px 20px rgba(0,0,0,0.10)' : 'none',
41 transition: a.hoverLift ? 'transform 0.3s ease, box-shadow 0.3s ease' : 'none',
42 };
43
44 var contentStyle = {
45 padding: a.cardPadding + 'px',
46 };
47
48 return el('div', { style: cardStyle, className: 'bkmc-card' },
49 a.showImage ? el('div', {
50 style: {
51 width: '100%',
52 height: a.imageHeight + 'px',
53 overflow: 'hidden',
54 background: '#f3f4f6',
55 }
56 }, el('img', {
57 src: card.imageUrl || PLACEHOLDER,
58 alt: card.imageAlt || '',
59 style: { width: '100%', height: '100%', objectFit: 'cover', display: 'block' }
60 })) : null,
61 el('div', { style: contentStyle },
62 a.showTag && card.tag ? el('span', {
63 className: 'bkmc-tag',
64 style: {
65 display: 'inline-block',
66 padding: '2px 10px',
67 borderRadius: '20px',
68 background: a.tagBg,
69 color: a.tagColor,
70 marginBottom: '10px',
71 }
72 }, card.tag) : null,
73 el('h3', {
74 className: 'bkmc-heading',
75 style: {
76 margin: '0 0 8px',
77 color: a.headingColor,
78 }
79 }, card.heading || __('Card Heading', 'blockenberg')),
80 a.showText && card.text ? el('p', {
81 className: 'bkmc-text',
82 style: {
83 margin: '0 0 12px',
84 color: a.textColor,
85 }
86 }, card.text) : null,
87 a.showLink && card.linkLabel ? el('a', {
88 href: card.link || '#',
89 className: 'bkmc-link',
90 style: {
91 color: a.linkColor,
92 textDecoration: 'none',
93 }
94 }, card.linkLabel) : null
95 )
96 );
97 }
98
99 /* ── Inspector card editor row ─────────────────────────────────────────── */
100 function CardEditor(props) {
101 var idx = props.idx;
102 var card = props.card;
103 var onChange = props.onChange;
104 var onRemove = props.onRemove;
105 var a = props.a;
106
107 var open = props.openIdx === idx;
108 var setOpen = props.setOpen;
109
110 return el(PanelBody, {
111 title: (card.heading || __('Card', 'blockenberg') + ' ' + (idx + 1)),
112 initialOpen: false,
113 opened: open,
114 onToggle: function () { setOpen(open ? -1 : idx); }
115 },
116 el(MediaUploadCheck, null,
117 el(MediaUpload, {
118 onSelect: function (media) {
119 onChange(idx, { imageUrl: media.url, imageId: media.id, imageAlt: media.alt || '' });
120 },
121 allowedTypes: ['image'],
122 value: card.imageId,
123 render: function (ref) {
124 return el(Button, {
125 onClick: ref.open,
126 variant: card.imageUrl ? 'secondary' : 'primary',
127 size: 'small',
128 style: { marginBottom: '8px' }
129 }, card.imageUrl ? __('Replace Image', 'blockenberg') : __('Add Image', 'blockenberg'));
130 }
131 })
132 ),
133 el(TextControl, {
134 label: __('Tag / Category', 'blockenberg'),
135 value: card.tag || '',
136 onChange: function (v) { onChange(idx, { tag: v }); }
137 }),
138 el(TextControl, {
139 label: __('Heading', 'blockenberg'),
140 value: card.heading || '',
141 onChange: function (v) { onChange(idx, { heading: v }); }
142 }),
143 el(TextControl, {
144 label: __('Description', 'blockenberg'),
145 value: card.text || '',
146 onChange: function (v) { onChange(idx, { text: v }); }
147 }),
148 el(TextControl, {
149 label: __('Link URL', 'blockenberg'),
150 type: 'url',
151 value: card.link || '',
152 onChange: function (v) { onChange(idx, { link: v }); }
153 }),
154 el(TextControl, {
155 label: __('Link Label', 'blockenberg'),
156 value: card.linkLabel || 'Learn more →',
157 onChange: function (v) { onChange(idx, { linkLabel: v }); }
158 }),
159 el(Button, {
160 variant: 'tertiary',
161 isDestructive: true,
162 onClick: function () { onRemove(idx); }
163 }, __('Remove Card', 'blockenberg'))
164 );
165 }
166
167 /* ── Register ──────────────────────────────────────────────────────────── */
168 registerBlockType('blockenberg/marquee-cards', {
169 edit: function (props) {
170 var a = props.attributes;
171 var set = props.setAttributes;
172 var openIdx = useState(-1);
173
174 function updateCard(idx, patch) {
175 var newCards = a.cards.map(function (c, i) {
176 return i === idx ? Object.assign({}, c, patch) : c;
177 });
178 set({ cards: newCards });
179 }
180
181 function removeCard(idx) {
182 set({ cards: a.cards.filter(function (_, i) { return i !== idx; }) });
183 }
184
185 function addCard() {
186 set({ cards: a.cards.concat([{
187 imageUrl: '', imageId: 0, imageAlt: '',
188 tag: 'New', heading: 'New Card', text: 'Add your description here.', link: '', linkLabel: 'Learn more →'
189 }]) });
190 openIdx[1](a.cards.length);
191 }
192
193 var blockProps = useBlockProps((function () {
194 var tv = getTypoCssVars();
195 var s = {
196 paddingTop: a.paddingY + 'px',
197 paddingBottom: a.paddingY + 'px',
198 overflow: 'hidden',
199 position: 'relative',
200 };
201 if (a.wrapperBg) s.background = a.wrapperBg;
202 Object.assign(s, tv(a.headingTypo, '--bkbg-mkc-hd-'));
203 Object.assign(s, tv(a.textTypo, '--bkbg-mkc-tx-'));
204 Object.assign(s, tv(a.tagTypo, '--bkbg-mkc-tg-'));
205 return { className: 'bkmc-outer', style: s };
206 })());
207
208 /* Preview: show all cards in a non-scrolling row in editor */
209 var trackStyle = {
210 display: 'flex',
211 gap: a.cardGap + 'px',
212 overflowX: 'auto',
213 paddingBottom: '12px',
214 };
215
216 return el(wp.element.Fragment, null,
217 el(InspectorControls, null,
218
219 /* Cards */
220 el(PanelBody, { title: __('Cards', 'blockenberg'), initialOpen: true },
221 a.cards.map(function (card, idx) {
222 return el(CardEditor, {
223 key: idx, idx: idx, card: card, a: a,
224 onChange: updateCard,
225 onRemove: removeCard,
226 openIdx: openIdx[0],
227 setOpen: openIdx[1],
228 });
229 }),
230 el(Button, {
231 variant: 'primary',
232 onClick: addCard,
233 style: { marginTop: '12px', width: '100%', justifyContent: 'center' }
234 }, __('+ Add Card', 'blockenberg'))
235 ),
236
237 /* Scroll settings */
238 el(PanelBody, { title: __('Scroll', 'blockenberg'), initialOpen: false },
239 el(RangeControl, {
240 label: __('Speed (s for full loop)', 'blockenberg'),
241 value: a.speed,
242 min: 5, max: 120,
243 onChange: function (v) { set({ speed: v }); }
244 }),
245 el(SelectControl, {
246 label: __('Direction', 'blockenberg'),
247 value: a.direction,
248 options: [
249 { label: __('Left', 'blockenberg'), value: 'left' },
250 { label: __('Right', 'blockenberg'), value: 'right' },
251 ],
252 onChange: function (v) { set({ direction: v }); }
253 }),
254 el(ToggleControl, {
255 label: __('Pause on Hover', 'blockenberg'),
256 checked: a.pauseOnHover,
257 onChange: function (v) { set({ pauseOnHover: v }); },
258 __nextHasNoMarginBottom: true
259 }),
260 el(ToggleControl, {
261 label: __('Two Rows (second row reversed)', 'blockenberg'),
262 checked: a.twoRows,
263 onChange: function (v) { set({ twoRows: v }); },
264 __nextHasNoMarginBottom: true
265 }),
266 el(ToggleControl, {
267 label: __('Fade Edges', 'blockenberg'),
268 checked: a.fadeEdges,
269 onChange: function (v) { set({ fadeEdges: v }); },
270 __nextHasNoMarginBottom: true
271 }),
272 el(RangeControl, {
273 label: __('Wrapper Padding Y (px)', 'blockenberg'),
274 value: a.paddingY,
275 min: 0, max: 120,
276 onChange: function (v) { set({ paddingY: v }); }
277 })
278 ),
279
280 /* Card appearance */
281 el(PanelBody, { title: __('Card Appearance', 'blockenberg'), initialOpen: false },
282 el(RangeControl, {
283 label: __('Card Width (px)', 'blockenberg'),
284 value: a.cardWidth,
285 min: 140, max: 600,
286 onChange: function (v) { set({ cardWidth: v }); }
287 }),
288 el(RangeControl, {
289 label: __('Card Gap (px)', 'blockenberg'),
290 value: a.cardGap,
291 min: 0, max: 60,
292 onChange: function (v) { set({ cardGap: v }); }
293 }),
294 el(RangeControl, {
295 label: __('Card Border Radius (px)', 'blockenberg'),
296 value: a.cardRadius,
297 min: 0, max: 40,
298 onChange: function (v) { set({ cardRadius: v }); }
299 }),
300 el(RangeControl, {
301 label: __('Card Padding (px)', 'blockenberg'),
302 value: a.cardPadding,
303 min: 8, max: 60,
304 onChange: function (v) { set({ cardPadding: v }); }
305 }),
306 el(RangeControl, {
307 label: __('Border Width (px)', 'blockenberg'),
308 value: a.cardBorderWidth,
309 min: 0, max: 4,
310 onChange: function (v) { set({ cardBorderWidth: v }); }
311 }),
312 el(ToggleControl, {
313 label: __('Card Shadow', 'blockenberg'),
314 checked: a.cardShadow,
315 onChange: function (v) { set({ cardShadow: v }); },
316 __nextHasNoMarginBottom: true
317 }),
318 el(ToggleControl, {
319 label: __('Lift on Hover', 'blockenberg'),
320 checked: a.hoverLift,
321 onChange: function (v) { set({ hoverLift: v }); },
322 __nextHasNoMarginBottom: true
323 })
324 ),
325
326 /* Image */
327 el(PanelBody, { title: __('Image', 'blockenberg'), initialOpen: false },
328 el(ToggleControl, {
329 label: __('Show Image', 'blockenberg'),
330 checked: a.showImage,
331 onChange: function (v) { set({ showImage: v }); },
332 __nextHasNoMarginBottom: true
333 }),
334 a.showImage ? el(RangeControl, {
335 label: __('Image Height (px)', 'blockenberg'),
336 value: a.imageHeight,
337 min: 60, max: 400,
338 onChange: function (v) { set({ imageHeight: v }); }
339 }) : null,
340 el(ToggleControl, {
341 label: __('Show Tag', 'blockenberg'),
342 checked: a.showTag,
343 onChange: function (v) { set({ showTag: v }); },
344 __nextHasNoMarginBottom: true
345 }),
346 el(ToggleControl, {
347 label: __('Show Description', 'blockenberg'),
348 checked: a.showText,
349 onChange: function (v) { set({ showText: v }); },
350 __nextHasNoMarginBottom: true
351 }),
352 el(ToggleControl, {
353 label: __('Show Link', 'blockenberg'),
354 checked: a.showLink,
355 onChange: function (v) { set({ showLink: v }); },
356 __nextHasNoMarginBottom: true
357 })
358 ),
359
360 /* Typography */
361 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
362 getTypoControl() && el(getTypoControl(), { label: __('Heading', 'blockenberg'), value: a.headingTypo || {}, onChange: function (v) { set({ headingTypo: v }); } }),
363 getTypoControl() && el(getTypoControl(), { label: __('Text', 'blockenberg'), value: a.textTypo || {}, onChange: function (v) { set({ textTypo: v }); } }),
364 getTypoControl() && el(getTypoControl(), { label: __('Tag', 'blockenberg'), value: a.tagTypo || {}, onChange: function (v) { set({ tagTypo: v }); } })
365 ),
366
367 /* Colors */
368 el(PanelColorSettings, {
369 title: __('Colors', 'blockenberg'),
370 initialOpen: false,
371 colorSettings: [
372 { value: a.wrapperBg, onChange: function (v) { set({ wrapperBg: v || '' }); }, label: __('Wrapper Background', 'blockenberg') },
373 { value: a.cardBg, onChange: function (v) { set({ cardBg: v || '#ffffff' }); }, label: __('Card Background', 'blockenberg') },
374 { value: a.cardBorder, onChange: function (v) { set({ cardBorder: v || '#e5e7eb' }); }, label: __('Card Border', 'blockenberg') },
375 { value: a.tagBg, onChange: function (v) { set({ tagBg: v || '#ede9fe' }); }, label: __('Tag Background', 'blockenberg') },
376 { value: a.tagColor, onChange: function (v) { set({ tagColor: v || '#6c3fb5' }); }, label: __('Tag Text', 'blockenberg') },
377 { value: a.headingColor,onChange: function (v) { set({ headingColor:v || '#111827' }); }, label: __('Heading', 'blockenberg') },
378 { value: a.textColor, onChange: function (v) { set({ textColor: v || '#6b7280' }); }, label: __('Description', 'blockenberg') },
379 { value: a.linkColor, onChange: function (v) { set({ linkColor: v || '#6c3fb5' }); }, label: __('Link', 'blockenberg') },
380 ]
381 })
382 ),
383
384 /* Canvas */
385 el('div', blockProps,
386 el('div', { style: { fontSize: '11px', color: '#9ca3af', marginBottom: '8px', textAlign: 'center' } },
387 __('Marquee Cards — preview (scrolling is frontend-only)', 'blockenberg')
388 ),
389 el('div', { style: trackStyle },
390 a.cards.map(function (card, i) {
391 return el(CardPreview, { key: i, a: a, card: card });
392 })
393 )
394 )
395 );
396 },
397
398 save: function (props) {
399 var a = props.attributes;
400
401 function renderCard(card, i, cloned) {
402 return el('div', {
403 key: (cloned ? 'c' : '') + i,
404 className: 'bkmc-card',
405 'aria-hidden': cloned ? 'true' : undefined,
406 style: 'width:' + a.cardWidth + 'px;border-radius:' + a.cardRadius + 'px;border:' + a.cardBorderWidth + 'px solid ' + a.cardBorder + ';background:' + a.cardBg + ';flex-shrink:0;overflow:hidden;box-shadow:' + (a.cardShadow ? '0 4px 20px rgba(0,0,0,0.10)' : 'none') + ';',
407 },
408 a.showImage ? el('div', {
409 style: 'width:100%;height:' + a.imageHeight + 'px;overflow:hidden;background:#f3f4f6;'
410 }, card.imageUrl ? el('img', { src: card.imageUrl, alt: card.imageAlt || '', style: 'width:100%;height:100%;object-fit:cover;display:block;' }) : null) : null,
411 el('div', { style: 'padding:' + a.cardPadding + 'px;' },
412 a.showTag && card.tag ? el('span', {
413 className: 'bkmc-tag',
414 style: 'display:inline-block;padding:2px 10px;border-radius:20px;background:' + a.tagBg + ';color:' + a.tagColor + ';margin-bottom:10px;'
415 }, card.tag) : null,
416 el('h3', { className: 'bkmc-heading', style: 'margin:0 0 8px;color:' + a.headingColor + ';' }, card.heading),
417 a.showText && card.text ? el('p', { className: 'bkmc-text', style: 'margin:0 0 12px;color:' + a.textColor + ';' }, card.text) : null,
418 a.showLink && card.link && card.linkLabel ? el('a', { href: card.link, className: 'bkmc-link', style: 'color:' + a.linkColor + ';text-decoration:none;' }, card.linkLabel) : null
419 )
420 );
421 }
422
423 var blockProps = wp.blockEditor.useBlockProps.save((function () {
424 var tv = getTypoCssVars();
425 var s = {};
426 if (a.wrapperBg) s.background = a.wrapperBg;
427 s.paddingTop = a.paddingY + 'px';
428 s.paddingBottom = a.paddingY + 'px';
429 Object.assign(s, tv(a.headingTypo, '--bkbg-mkc-hd-'));
430 Object.assign(s, tv(a.textTypo, '--bkbg-mkc-tx-'));
431 Object.assign(s, tv(a.tagTypo, '--bkbg-mkc-tg-'));
432 return {
433 className: 'bkmc-outer',
434 style: s,
435 'data-speed': a.speed,
436 'data-direction': a.direction,
437 'data-pause': String(a.pauseOnHover),
438 'data-card-width': a.cardWidth,
439 'data-card-gap': a.cardGap,
440 'data-hover-lift': String(a.hoverLift),
441 'data-fade': String(a.fadeEdges),
442 };
443 })());
444
445 var row1 = el('div', {
446 className: 'bkmc-track',
447 'data-row': '1',
448 },
449 a.cards.map(function (card, i) { return renderCard(card, i, false); }),
450 // Clone set for seamless loop
451 a.cards.map(function (card, i) { return renderCard(card, i, true); })
452 );
453
454 var row2 = a.twoRows ? el('div', {
455 className: 'bkmc-track',
456 'data-row': '2',
457 'data-reverse': 'true',
458 },
459 a.cards.map(function (card, i) { return renderCard(card, i, false); }),
460 a.cards.map(function (card, i) { return renderCard(card, i, true); })
461 ) : null;
462
463 return el('div', blockProps, row1, row2);
464 }
465 });
466 }() );
467