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 / sticky-scroll / index.js

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

502 lines 25.6 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 Fragment = wp.element.Fragment;
4 var registerBlockType = wp.blocks.registerBlockType;
5 var __ = wp.i18n.__;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
8 var MediaUpload = wp.blockEditor.MediaUpload;
9 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
10 var useBlockProps = wp.blockEditor.useBlockProps;
11 var PanelBody = wp.components.PanelBody;
12 var RangeControl = wp.components.RangeControl;
13 var SelectControl = wp.components.SelectControl;
14 var TextControl = wp.components.TextControl;
15 var TextareaControl = wp.components.TextareaControl;
16 var ToggleControl = wp.components.ToggleControl;
17 var Button = wp.components.Button;
18 var ColorPicker = wp.components.ColorPicker;
19 var Popover = wp.components.Popover;
20 var useState = wp.element.useState;
21 var Fragment = wp.element.Fragment;
22
23 var _tc, _tv;
24 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
25 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
26
27 function uid() { return 'id-' + Math.random().toString(36).slice(2, 8); }
28
29 // Image panel with placeholder
30 function ImagePanel(props) {
31 var imageUrl = props.imageUrl;
32 var radius = props.radius;
33 var shadow = props.shadow;
34 var placeholderColor = props.placeholderColor;
35
36 var imgStyle = {
37 width: '100%',
38 borderRadius: radius + 'px',
39 boxShadow: shadow ? '0 24px 60px rgba(0,0,0,0.15)' : 'none',
40 display: 'block'
41 };
42
43 if (imageUrl) {
44 return el('img', { src: imageUrl, alt: '', style: imgStyle });
45 }
46
47 return el('div', {
48 style: {
49 width: '100%',
50 aspectRatio: '16 / 10',
51 borderRadius: radius + 'px',
52 background: placeholderColor || '#e5e7eb',
53 display: 'flex',
54 alignItems: 'center',
55 justifyContent: 'center',
56 boxShadow: shadow ? '0 24px 60px rgba(0,0,0,0.15)' : 'none'
57 }
58 },
59 el('svg', { width: '48', height: '48', viewBox: '0 0 24 24', style: { opacity: 0.3 } },
60 el('path', { d: 'M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-2 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z', fill: '#9ca3af' })
61 )
62 );
63 }
64
65 // Single text panel (scrolling side)
66 function TextPanel(props) {
67 var item = props.item;
68 var attrs = props.attrs;
69 var isActive = props.isActive;
70
71 var panelStyle = {
72 paddingTop: attrs.panelPaddingY + 'px',
73 paddingBottom: attrs.panelPaddingY + 'px',
74 opacity: isActive ? 1 : 0.4,
75 transition: 'opacity 0.3s'
76 };
77
78 return el('div', { className: 'bkss-panel' + (isActive ? ' bkss-active' : ''), style: panelStyle },
79 // Accent dot
80 attrs.showAccentDot && el('div', {
81 style: {
82 width: '10px', height: '10px', borderRadius: '50%',
83 background: isActive ? (item.accentColor || attrs.eyebrowColor) : (attrs.dotColor || '#e5e7eb'),
84 marginBottom: '16px',
85 transition: 'background 0.3s',
86 flexShrink: 0
87 }
88 }),
89 // Eyebrow
90 attrs.showEyebrow && item.eyebrow && el('div', {
91 className: 'bkss-eyebrow',
92 style: { color: item.accentColor || attrs.eyebrowColor, marginBottom: '8px' }
93 }, item.eyebrow),
94 // Title
95 el('h3', {
96 className: 'bkss-title',
97 style: { margin: '0 0 12px' }
98 }, item.title || __('Feature title', 'blockenberg')),
99 // Description
100 el('p', {
101 className: 'bkss-desc',
102 style: { margin: 0 }
103 }, item.desc || __('Describe this feature in a sentence or two…', 'blockenberg'))
104 );
105 }
106
107 var BkbgColorSwatch = function (props) {
108 var _st = useState(false); var isOpen = _st[0]; var setOpen = _st[1];
109 return el(Fragment, {},
110 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' } },
111 el('span', { style: { fontSize: '11px', fontWeight: 500, textTransform: 'uppercase', color: '#1e1e1e' } }, props.label),
112 el('button', {
113 type: 'button',
114 onClick: function () { setOpen(!isOpen); },
115 style: { width: '28px', height: '28px', borderRadius: '4px', border: '1px solid #ccc', background: props.value || '#ffffff', cursor: 'pointer', padding: 0 }
116 })
117 ),
118 isOpen && el(Popover, { onClose: function () { setOpen(false); } },
119 el('div', { style: { padding: '8px' } },
120 el(ColorPicker, { color: props.value, onChangeComplete: function (c) { props.onChange(c.hex); }, enableAlpha: true })
121 )
122 )
123 );
124 };
125
126 registerBlockType('blockenberg/sticky-scroll', {
127
128 edit: function (props) {
129 var attrs = props.attributes;
130 var setAttr = props.setAttributes;
131 var items = attrs.items || [];
132
133 function updateItem(i, key, val) {
134 var next = items.map(function (it, j) {
135 if (j !== i) return it;
136 var u = Object.assign({}, it); u[key] = val; return u;
137 });
138 setAttr({ items: next });
139 }
140
141 function removeItem(i) {
142 setAttr({ items: items.filter(function (_, j) { return j !== i; }) });
143 }
144
145 function addItem() {
146 setAttr({ items: items.concat([{
147 id: uid(), eyebrow: 'Step 0' + (items.length + 1),
148 title: 'New feature', desc: 'Describe this feature.',
149 imageUrl: '', imageId: 0, imageAlt: '', accentColor: '#6c3fb5'
150 }]) });
151 }
152
153 var blockProps = useBlockProps((function() {
154 var _tvf = getTypoCssVars();
155 var s = {
156 paddingTop: attrs.paddingTop + 'px',
157 paddingBottom: attrs.paddingBottom + 'px',
158 '--bkss-title-color': attrs.titleColor || '#111827',
159 '--bkss-desc-color': attrs.descColor || '#4b5563',
160 '--bkss-title-fw': attrs.titleFontWeight || '700',
161 '--bkss-desc-fw': attrs.descFontWeight || '400',
162 '--bkss-eb-fw': attrs.eyebrowFontWeight || '600',
163 '--bkss-title-lh': attrs.titleLineHeight || 1.2,
164 '--bkss-desc-lh': attrs.descLineHeight || 1.6
165 };
166 if (attrs.bgColor) s.background = attrs.bgColor;
167 if (_tvf) {
168 Object.assign(s, _tvf(attrs.eyebrowTypo, '--bkss-eb-'));
169 Object.assign(s, _tvf(attrs.titleTypo, '--bkss-tt-'));
170 Object.assign(s, _tvf(attrs.descTypo, '--bkss-ds-'));
171 }
172 return { className: 'bkss-wrap', style: s };
173 })());
174 var isPinnedLeft = attrs.pinnedSide !== 'right';
175 var pw = attrs.pinnedWidth;
176
177 // Editor: static stacked preview (not actually sticky in editor)
178 var pinnedColStyle = {
179 width: pw + '%',
180 flexShrink: 0
181 };
182 var scrollColStyle = {
183 flex: 1
184 };
185 var rowStyle = {
186 display: 'flex',
187 gap: attrs.columnGap + 'px',
188 alignItems: 'flex-start'
189 };
190
191 // Show first item's image in pinned column in editor
192 var firstItem = items[0] || {};
193
194 return el(Fragment, null,
195 el(InspectorControls, null,
196
197 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
198 el(SelectControl, {
199 label: __('Pinned side', 'blockenberg'),
200 value: attrs.pinnedSide,
201 options: [
202 { label: 'Left (image pinned)', value: 'left' },
203 { label: 'Right (image pinned)', value: 'right' }
204 ],
205 onChange: function (v) { setAttr({ pinnedSide: v }); }
206 }),
207 el(RangeControl, {
208 label: __('Pinned column width (%)', 'blockenberg'),
209 value: attrs.pinnedWidth, min: 30, max: 70,
210 onChange: function (v) { setAttr({ pinnedWidth: v }); }
211 }),
212 el(RangeControl, {
213 label: __('Column gap (px)', 'blockenberg'),
214 value: attrs.columnGap, min: 16, max: 120,
215 onChange: function (v) { setAttr({ columnGap: v }); }
216 }),
217 el(RangeControl, {
218 label: __('Panel vertical padding (px)', 'blockenberg'),
219 value: attrs.panelPaddingY, min: 16, max: 200,
220 onChange: function (v) { setAttr({ panelPaddingY: v }); }
221 }),
222 el(RangeControl, {
223 label: __('Sticky top offset (px)', 'blockenberg'),
224 value: attrs.stickyTop, min: 0, max: 200,
225 onChange: function (v) { setAttr({ stickyTop: v }); }
226 }),
227 el(RangeControl, {
228 label: __('Section padding top (px)', 'blockenberg'),
229 value: attrs.paddingTop, min: 0, max: 200,
230 onChange: function (v) { setAttr({ paddingTop: v }); }
231 }),
232 el(RangeControl, {
233 label: __('Section padding bottom (px)', 'blockenberg'),
234 value: attrs.paddingBottom, min: 0, max: 200,
235 onChange: function (v) { setAttr({ paddingBottom: v }); }
236 })
237 ),
238
239 el(PanelBody, { title: __('Visual', 'blockenberg'), initialOpen: false },
240 el(ToggleControl, {
241 label: __('Show eyebrow text', 'blockenberg'),
242 checked: attrs.showEyebrow,
243 onChange: function (v) { setAttr({ showEyebrow: v }); },
244 __nextHasNoMarginBottom: true
245 }),
246 el(ToggleControl, {
247 label: __('Show accent dot', 'blockenberg'),
248 checked: attrs.showAccentDot,
249 onChange: function (v) { setAttr({ showAccentDot: v }); },
250 __nextHasNoMarginBottom: true
251 }),
252 el(ToggleControl, {
253 label: __('Image shadow', 'blockenberg'),
254 checked: attrs.imageShadow,
255 onChange: function (v) { setAttr({ imageShadow: v }); },
256 __nextHasNoMarginBottom: true
257 }),
258 el(RangeControl, {
259 label: __('Image border radius (px)', 'blockenberg'),
260 value: attrs.imageRadius, min: 0, max: 48,
261 onChange: function (v) { setAttr({ imageRadius: v }); }
262 }),
263 el(ToggleControl, {
264 label: __('Highlight active panel on scroll', 'blockenberg'),
265 checked: attrs.activateOnScroll,
266 onChange: function (v) { setAttr({ activateOnScroll: v }); },
267 __nextHasNoMarginBottom: true
268 })
269 ),
270
271 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
272 getTypoControl() && getTypoControl()({ label: __('Eyebrow', 'blockenberg'), value: attrs.eyebrowTypo, onChange: function (v) { setAttr({ eyebrowTypo: v }); } }),
273 getTypoControl() && getTypoControl()({ label: __('Title', 'blockenberg'), value: attrs.titleTypo, onChange: function (v) { setAttr({ titleTypo: v }); } }),
274 getTypoControl() && getTypoControl()({ label: __('Description', 'blockenberg'), value: attrs.descTypo, onChange: function (v) { setAttr({ descTypo: v }); } })
275 ),
276
277 el(PanelColorSettings, {
278 title: __('Colors', 'blockenberg'),
279 initialOpen: false,
280 colorSettings: [
281 { label: __('Eyebrow / Accent', 'blockenberg'), value: attrs.eyebrowColor, onChange: function (v) { setAttr({ eyebrowColor: v || '#6c3fb5' }); } },
282 { label: __('Title', 'blockenberg'), value: attrs.titleColor, onChange: function (v) { setAttr({ titleColor: v || '#111827' }); } },
283 { label: __('Description', 'blockenberg'), value: attrs.descColor, onChange: function (v) { setAttr({ descColor: v || '#4b5563' }); } },
284 { label: __('Dot (inactive)', 'blockenberg'), value: attrs.dotColor, onChange: function (v) { setAttr({ dotColor: v || '#e5e7eb' }); } },
285 { label: __('Dot (active)', 'blockenberg'), value: attrs.dotActiveColor, onChange: function (v) { setAttr({ dotActiveColor: v || '#6c3fb5' }); } },
286 { label: __('Image placeholder', 'blockenberg'), value: attrs.imagePlaceholderColor, onChange: function (v) { setAttr({ imagePlaceholderColor: v || '#e5e7eb' }); } },
287 { label: __('Background', 'blockenberg'), value: attrs.bgColor, onChange: function (v) { setAttr({ bgColor: v || '' }); } }
288 ]
289 }),
290
291 // Items editor
292 el(PanelBody, { title: __('Panels (' + items.length + ')', 'blockenberg'), initialOpen: false },
293 items.map(function (item, i) {
294 return el(PanelBody, {
295 key: item.id,
296 title: item.title || (__('Panel', 'blockenberg') + ' ' + (i + 1)),
297 initialOpen: i === 0
298 },
299 el(TextControl, {
300 label: __('Eyebrow', 'blockenberg'),
301 value: item.eyebrow,
302 onChange: function (v) { updateItem(i, 'eyebrow', v); }
303 }),
304 el(TextControl, {
305 label: __('Title', 'blockenberg'),
306 value: item.title,
307 onChange: function (v) { updateItem(i, 'title', v); }
308 }),
309 el(TextareaControl, {
310 label: __('Description', 'blockenberg'),
311 rows: 3,
312 value: item.desc,
313 onChange: function (v) { updateItem(i, 'desc', v); }
314 }),
315 el(BkbgColorSwatch, {
316 label: __('Accent color (hex)', 'blockenberg'),
317 value: item.accentColor,
318 onChange: function (v) { updateItem(i, 'accentColor', v); }
319 }),
320 el(MediaUploadCheck, null,
321 el(MediaUpload, {
322 onSelect: function (media) {
323 updateItem(i, 'imageUrl', media.url);
324 updateItem(i, 'imageId', media.id);
325 updateItem(i, 'imageAlt', media.alt || '');
326 },
327 allowedTypes: ['image'],
328 value: item.imageId,
329 render: function (rp) {
330 return el('div', null,
331 item.imageUrl && el('img', { src: item.imageUrl, style: { width: '100%', borderRadius: '6px', marginBottom: '8px' } }),
332 el(Button, { variant: 'secondary', isSmall: true, onClick: rp.open },
333 item.imageUrl ? __('Change image', 'blockenberg') : __('Upload image', 'blockenberg')
334 ),
335 item.imageUrl && el(Button, {
336 variant: 'secondary', isDestructive: true, isSmall: true, style: { marginLeft: '6px' },
337 onClick: function () { updateItem(i, 'imageUrl', ''); updateItem(i, 'imageId', 0); }
338 }, __('Remove', 'blockenberg'))
339 );
340 }
341 })
342 ),
343 items.length > 1 && el(Button, {
344 variant: 'secondary', isDestructive: true, isSmall: true, style: { marginTop: '8px' },
345 onClick: function () { removeItem(i); }
346 }, __('Remove panel', 'blockenberg'))
347 );
348 }),
349 el(Button, {
350 variant: 'primary', isSmall: true,
351 style: { marginTop: '12px', width: '100%', justifyContent: 'center' },
352 onClick: addItem
353 }, __('+ Add panel', 'blockenberg'))
354 )
355 ),
356
357 el('div', blockProps,
358 el('div', { className: 'bkss-layout', style: rowStyle },
359 // Pinned column (image)
360 isPinnedLeft && el('div', { className: 'bkss-pinned', style: pinnedColStyle },
361 el(ImagePanel, { imageUrl: firstItem.imageUrl, radius: attrs.imageRadius, shadow: attrs.imageShadow, placeholderColor: attrs.imagePlaceholderColor })
362 ),
363
364 // Scrolling column (text panels)
365 el('div', { className: 'bkss-scroll-col', style: scrollColStyle },
366 items.map(function (item, i) {
367 return el(TextPanel, { key: item.id, item: item, attrs: attrs, isActive: i === 0 });
368 })
369 ),
370
371 // Pinned column right
372 !isPinnedLeft && el('div', { className: 'bkss-pinned', style: pinnedColStyle },
373 el(ImagePanel, { imageUrl: firstItem.imageUrl, radius: attrs.imageRadius, shadow: attrs.imageShadow, placeholderColor: attrs.imagePlaceholderColor })
374 )
375 )
376 )
377 );
378 },
379
380 save: function (props) {
381 var a = props.attributes;
382 var items = a.items || [];
383 var isPinnedLeft = a.pinnedSide !== 'right';
384 var pw = a.pinnedWidth;
385
386 var wrapStyle = (function() {
387 var _tvf = getTypoCssVars();
388 var s = {
389 paddingTop: a.paddingTop + 'px',
390 paddingBottom: a.paddingBottom + 'px',
391 '--bkss-sticky-top': a.stickyTop + 'px',
392 '--bkss-pinned-w': pw + '%',
393 '--bkss-gap': a.columnGap + 'px',
394 '--bkss-panel-py': a.panelPaddingY + 'px',
395 '--bkss-dot-c': a.dotColor || '#e5e7eb',
396 '--bkss-dot-active': a.dotActiveColor || '#6c3fb5',
397 '--bkss-title-color': a.titleColor || '#111827',
398 '--bkss-desc-color': a.descColor || '#4b5563',
399 '--bkss-title-fw': a.titleFontWeight || '700',
400 '--bkss-desc-fw': a.descFontWeight || '400',
401 '--bkss-eb-fw': a.eyebrowFontWeight || '600',
402 '--bkss-title-lh': a.titleLineHeight || 1.2,
403 '--bkss-desc-lh': a.descLineHeight || 1.6
404 };
405 if (a.bgColor) s.background = a.bgColor;
406 if (_tvf) {
407 Object.assign(s, _tvf(a.eyebrowTypo, '--bkss-eb-'));
408 Object.assign(s, _tvf(a.titleTypo, '--bkss-tt-'));
409 Object.assign(s, _tvf(a.descTypo, '--bkss-ds-'));
410 }
411 return s;
412 })();
413
414 var blockProps = wp.blockEditor.useBlockProps.save({
415 className: 'bkss-wrap',
416 style: wrapStyle,
417 'data-activate': a.activateOnScroll ? '1' : '0',
418 'data-pinned': a.pinnedSide
419 });
420
421 function renderImage(item) {
422 if (item.imageUrl) {
423 return el('img', {
424 src: item.imageUrl, alt: item.imageAlt || '',
425 style: { width: '100%', borderRadius: a.imageRadius + 'px', boxShadow: a.imageShadow ? '0 24px 60px rgba(0,0,0,0.15)' : 'none', display: 'block' }
426 });
427 }
428 return el('div', {
429 style: {
430 width: '100%', aspectRatio: '16 / 10',
431 borderRadius: a.imageRadius + 'px',
432 background: a.imagePlaceholderColor || '#e5e7eb',
433 boxShadow: a.imageShadow ? '0 24px 60px rgba(0,0,0,0.15)' : 'none'
434 }
435 });
436 }
437
438 return el('div', blockProps,
439 el('div', {
440 className: 'bkss-layout',
441 style: { display: 'flex', gap: a.columnGap + 'px', alignItems: 'flex-start' }
442 },
443 // Pinned (image) column
444 isPinnedLeft && el('div', {
445 className: 'bkss-pinned',
446 style: { width: pw + '%', flexShrink: 0, position: 'sticky', top: a.stickyTop + 'px' }
447 },
448 el('div', { className: 'bkss-image-wrap' },
449 renderImage(items[0] || {})
450 )
451 ),
452
453 // Scrolling column (panels)
454 el('div', { className: 'bkss-scroll-col', style: { flex: 1 } },
455 items.map(function (item, i) {
456 var panelStyle = {
457 paddingTop: a.panelPaddingY + 'px',
458 paddingBottom: a.panelPaddingY + 'px'
459 };
460 return el('div', {
461 key: item.id,
462 className: 'bkss-panel' + (i === 0 ? ' bkss-active' : ''),
463 style: panelStyle,
464 'data-image': item.imageUrl || '',
465 'data-idx': i
466 },
467 // The image for this panel (hidden, JS swaps it in)
468 a.showAccentDot && el('div', {
469 className: 'bkss-dot',
470 style: { width: '10px', height: '10px', borderRadius: '50%', background: i === 0 ? (item.accentColor || a.dotActiveColor) : (a.dotColor || '#e5e7eb'), marginBottom: '16px', transition: 'background 0.3s', '--bkss-accent': item.accentColor || a.dotActiveColor }
471 }),
472 a.showEyebrow && item.eyebrow && el('div', {
473 className: 'bkss-eyebrow',
474 style: { color: item.accentColor || a.eyebrowColor, marginBottom: '8px' }
475 }, item.eyebrow),
476 el('h3', {
477 className: 'bkss-title',
478 style: { margin: '0 0 12px' }
479 }, item.title),
480 el('p', {
481 className: 'bkss-desc',
482 style: { margin: 0 }
483 }, item.desc)
484 );
485 })
486 ),
487
488 // Pinned right
489 !isPinnedLeft && el('div', {
490 className: 'bkss-pinned',
491 style: { width: pw + '%', flexShrink: 0, position: 'sticky', top: a.stickyTop + 'px' }
492 },
493 el('div', { className: 'bkss-image-wrap' },
494 renderImage(items[0] || {})
495 )
496 )
497 )
498 );
499 }
500 });
501 }() );
502