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 / image-accordion / index.js

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

406 lines 25.0 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 __ = wp.i18n.__;
5 var useState = wp.element.useState;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var useBlockProps = wp.blockEditor.useBlockProps;
9 var MediaUpload = wp.blockEditor.MediaUpload;
10 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
11 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
12 var PanelBody = wp.components.PanelBody;
13 var ToggleControl = wp.components.ToggleControl;
14 var RangeControl = wp.components.RangeControl;
15 var SelectControl = wp.components.SelectControl;
16 var TextControl = wp.components.TextControl;
17 var ColorPicker = wp.components.ColorPicker;
18 var Popover = wp.components.Popover;
19 var Button = wp.components.Button;
20
21 var _TypographyControl, _typoCssVars;
22 function getTypographyControl() { return _TypographyControl || (_TypographyControl = window.bkbgTypographyControl); }
23 function getTypoCssVars() { return _typoCssVars || (_typoCssVars = window.bkbgTypoCssVars); }
24
25 /* ── Placeholder SVG ──────────────────────────────────────────────────── */
26 var PLACEHOLDER = 'data:image/svg+xml;utf8,' + encodeURIComponent(
27 '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">' +
28 '<rect width="400" height="300" fill="#374151"/>' +
29 '<text x="200" y="155" text-anchor="middle" fill="#9ca3af" font-size="16" font-family="sans-serif">Image</text>' +
30 '</svg>'
31 );
32
33 /* ── text position helpers ───────────────────────────────────────────── */
34 var posMap = {
35 'bottom-left': { justifyContent: 'flex-end', alignItems: 'flex-start' },
36 'bottom-center': { justifyContent: 'flex-end', alignItems: 'center' },
37 'bottom-right': { justifyContent: 'flex-end', alignItems: 'flex-end' },
38 'top-left': { justifyContent: 'flex-start', alignItems: 'flex-start' },
39 'center': { justifyContent: 'center', alignItems: 'center' },
40 };
41
42 function getPosStyle(pos) {
43 return posMap[pos] || posMap['bottom-left'];
44 }
45
46 function hexAlpha(hex, opacity) {
47 var h = (hex || '#000000').replace('#', '');
48 if (h.length === 3) { h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2]; }
49 var r = parseInt(h.slice(0,2),16), g = parseInt(h.slice(2,4),16), b = parseInt(h.slice(4,6),16);
50 return 'rgba(' + r + ',' + g + ',' + b + ',' + (opacity/100) + ')';
51 }
52
53 /* ── PanelEditor for a single panel ──────────────────────────────────── */
54 function PanelEditor(props) {
55 var panel = props.panel, idx = props.idx;
56 var onUpdate = props.onUpdate, onRemove = props.onRemove;
57 var expandState = useState(false);
58 var isOpen = expandState[0], setIsOpen = expandState[1];
59
60 return el('div', { style: { border: '1px solid #e5e7eb', borderRadius: '8px', marginBottom: '8px', overflow: 'hidden' } },
61 el('div', {
62 style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '8px', background: '#f9fafb', cursor: 'pointer' },
63 onClick: function () { setIsOpen(!isOpen); }
64 },
65 panel.imageUrl
66 ? el('img', { src: panel.imageUrl, style: { width: '40px', height: '28px', objectFit: 'cover', borderRadius: '3px' } })
67 : el('div', { style: { width: '40px', height: '28px', background: '#e5e7eb', borderRadius: '3px' } }),
68 el('span', { style: { flex: 1, fontSize: '13px', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
69 panel.title || ('Panel ' + (idx + 1))),
70 el('span', { style: { fontSize: '11px', color: '#9ca3af' } }, isOpen ? '' : ''),
71 el(Button, { isSmall: true, variant: 'tertiary', isDestructive: true, onClick: function (e) { e.stopPropagation(); onRemove(idx); } }, '×')
72 ),
73 isOpen && el('div', { style: { padding: '12px', display: 'flex', flexDirection: 'column', gap: '8px' } },
74 el(MediaUploadCheck, null,
75 el(MediaUpload, {
76 onSelect: function (m) { onUpdate(idx, 'imageUrl', m.url); onUpdate(idx, 'imageId', m.id); onUpdate(idx, 'imageAlt', m.alt || ''); },
77 allowedTypes: ['image'],
78 value: panel.imageId,
79 render: function (ref) {
80 return el(Button, { variant: panel.imageUrl ? 'secondary' : 'primary', onClick: ref.open, isSmall: true, style: { width: '100%' } },
81 panel.imageUrl ? __('Replace Image', 'blockenberg') : __('Choose Image', 'blockenberg')
82 );
83 }
84 })
85 ),
86 el(TextControl, { label: __('Label', 'blockenberg'), value: panel.label, onChange: function (v) { onUpdate(idx, 'label', v); } }),
87 el(TextControl, { label: __('Title', 'blockenberg'), value: panel.title, onChange: function (v) { onUpdate(idx, 'title', v); } }),
88 el(TextControl, { label: __('Subtitle', 'blockenberg'), value: panel.subtitle, onChange: function (v) { onUpdate(idx, 'subtitle', v); } })
89 )
90 );
91 }
92
93 /* ── render a single panel div ────────────────────────────────────────── */
94 function renderPanel(panel, idx, a, isActive, isEdit) {
95 var posStyle = getPosStyle(a.textPosition);
96 var overlayBg = hexAlpha(a.overlayColor, isActive ? a.overlayHoverOpacity : a.overlayOpacity);
97 var textAlign = a.textPosition.includes('right') ? 'right' : a.textPosition.includes('center') ? 'center' : 'left';
98 var showText = a.textVisibility === 'always' || (a.textVisibility === 'expanded' && isActive) || (a.textVisibility === 'collapsed' && !isActive);
99
100 return el('div', {
101 key: idx,
102 className: 'bkia-panel' + (isActive ? ' bkia-active' : ''),
103 'data-index': idx,
104 style: {
105 flex: isActive ? a.expandedFlex : a.collapsedFlex,
106 position: 'relative',
107 overflow: 'hidden',
108 borderRadius: a.borderRadius + 'px',
109 cursor: isEdit ? 'default' : 'pointer',
110 background: panel.imageUrl ? 'none' : a.bgFallbackColor,
111 transition: 'flex ' + a.transitionDuration + 'ms ' + a.transitionEasing,
112 }
113 },
114 /* background image */
115 panel.imageUrl && el('img', {
116 className: 'bkia-img',
117 src: panel.imageUrl,
118 alt: panel.imageAlt || panel.title,
119 style: {
120 position: 'absolute', inset: '0', width: '100%', height: '100%',
121 objectFit: 'cover',
122 transition: a.scaleOnHover ? 'transform ' + a.transitionDuration + 'ms ' + a.transitionEasing : 'none',
123 transform: isActive && a.scaleOnHover ? 'scale(1.05)' : 'scale(1)',
124 }
125 }),
126 !panel.imageUrl && el('div', {
127 style: { position: 'absolute', inset: '0', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#6b7280', fontSize: '12px' }
128 }, 'Panel ' + (idx + 1)),
129 /* overlay */
130 el('div', {
131 className: 'bkia-overlay',
132 style: { position: 'absolute', inset: '0', background: overlayBg, transition: 'background ' + a.transitionDuration + 'ms ease', zIndex: 1 }
133 }),
134 /* text */
135 el('div', {
136 className: 'bkia-text',
137 style: {
138 position: 'absolute', inset: '0', zIndex: 2,
139 display: 'flex', flexDirection: 'column',
140 padding: '20px',
141 justifyContent: posStyle.justifyContent,
142 alignItems: posStyle.alignItems,
143 textAlign: textAlign,
144 opacity: showText ? 1 : 0,
145 transition: 'opacity 0.3s ease',
146 }
147 },
148 a.showLabel && panel.label && el('span', {
149 className: 'bkia-label',
150 style: {
151 display: 'inline-block', color: a.labelColor,
152 background: a.labelBg, borderRadius: '4px', padding: '2px 8px',
153 marginBottom: '6px',
154 }
155 }, panel.label),
156 a.showTitle && panel.title && el('p', {
157 className: 'bkia-title',
158 style: { margin: '0 0 4px', color: a.titleColor }
159 }, panel.title),
160 a.showSubtitle && panel.subtitle && el('p', {
161 className: 'bkia-subtitle',
162 style: { margin: '0', color: a.subtitleColor }
163 }, panel.subtitle),
164 a.showArrow && el('span', { className: 'bkia-arrow', style: { marginTop: '10px', color: a.titleColor, fontSize: '20px' } }, '')
165 )
166 );
167 }
168
169 /* ── colour-swatch + popover ─────────────────────────────── */
170 function BkbgColorSwatch(p) {
171 var st = useState(false);
172 var open = st[0], setOpen = st[1];
173 return el('div', { style:{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'4px 0', gap:'8px' } },
174 el('span', { style:{ fontSize:'12px', color:'#1e1e1e', flex:1, lineHeight:1.4 } }, p.label),
175 el('div', { style:{ position:'relative', flexShrink:0 } },
176 el('button', {
177 type:'button', title: p.value || 'none',
178 onClick: function(){ setOpen(!open); },
179 style:{ width:'28px', height:'28px', borderRadius:'4px',
180 border: open ? '2px solid #007cba' : '2px solid #ddd',
181 cursor:'pointer', padding:0, display:'block',
182 background: p.value || '#ffffff', flexShrink:0 }
183 }),
184 open && el(Popover, { position:'bottom left', onClose:function(){ setOpen(false); } },
185 el('div', { style:{ padding:'8px' }, onMouseDown:function(e){ e.stopPropagation(); } },
186 el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'6px' } },
187 el('strong', { style:{ fontSize:'12px' } }, p.label),
188 el(Button, { icon:'no-alt', isSmall:true, onClick:function(){ setOpen(false); } })
189 ),
190 el(ColorPicker, { color: p.value, enableAlpha:true, onChange: p.onChange })
191 )
192 )
193 )
194 );
195 }
196
197 registerBlockType('blockenberg/image-accordion', {
198 title: __('Image Accordion', 'blockenberg'),
199 icon: 'images-alt2',
200 category: 'bkbg-media',
201 description: __('Expandable image panels that expand on hover or click.', 'blockenberg'),
202
203 edit: function (props) {
204 var attributes = props.attributes;
205 var setAttributes = props.setAttributes;
206 var a = attributes;
207
208 var activeState = useState(a.defaultOpen);
209 var active = activeState[0], setActive = activeState[1];
210
211 function updatePanel(idx, key, val) {
212 var arr = a.panels.slice();
213 arr[idx] = Object.assign({}, arr[idx]);
214 arr[idx][key] = val;
215 setAttributes({ panels: arr });
216 }
217 function addPanel() {
218 var n = a.panels.length + 1;
219 setAttributes({ panels: a.panels.concat([{ imageUrl: '', imageId: 0, imageAlt: '', title: 'Panel ' + n, subtitle: 'Subtitle here', label: String(n).padStart(2,'0') }]) });
220 }
221 function removePanel(idx) {
222 setAttributes({ panels: a.panels.filter(function (_, i) { return i !== idx; }) });
223 }
224
225 var triggerOptions = [
226 { label: __('Hover', 'blockenberg'), value: 'hover' },
227 { label: __('Click', 'blockenberg'), value: 'click' },
228 ];
229 var directionOptions = [
230 { label: __('Horizontal', 'blockenberg'), value: 'horizontal' },
231 { label: __('Vertical', 'blockenberg'), value: 'vertical' },
232 ];
233 var easingOptions = [
234 { label: 'ease', value: 'ease' },
235 { label: 'ease-in-out', value: 'ease-in-out' },
236 { label: 'ease-out', value: 'ease-out' },
237 { label: 'linear', value: 'linear' },
238 ];
239 var textPosOptions = [
240 { label: __('Bottom Left', 'blockenberg'), value: 'bottom-left' },
241 { label: __('Bottom Center', 'blockenberg'), value: 'bottom-center' },
242 { label: __('Bottom Right', 'blockenberg'), value: 'bottom-right' },
243 { label: __('Top Left', 'blockenberg'), value: 'top-left' },
244 { label: __('Center', 'blockenberg'), value: 'center' },
245 ];
246 var textVisOptions = [
247 { label: __('Always visible', 'blockenberg'), value: 'always' },
248 { label: __('Visible when expanded', 'blockenberg'), value: 'expanded' },
249 { label: __('Visible when collapsed', 'blockenberg'), value: 'collapsed' },
250 ];
251 var weightOptions = [
252 { label: '400', value: 400 }, { label: '500', value: 500 },
253 { label: '600', value: 600 }, { label: '700', value: 700 }, { label: '800', value: 800 },
254 ];
255 var defaultOpenOptions = a.panels.map(function (p, i) { return { label: (p.title || 'Panel ' + (i+1)), value: i }; });
256
257 var blockProps = useBlockProps({ className: 'bkia-wrap', style: (function(){
258 var s = {};
259 if (a.titleSize) s['--bkia-title-sz'] = a.titleSize + 'px';
260 if (a.subtitleSize) s['--bkia-subtitle-sz'] = a.subtitleSize + 'px';
261 if (a.labelSize) s['--bkia-label-sz'] = a.labelSize + 'px';
262 if (a.titleWeight) s['--bkia-title-w'] = String(a.titleWeight);
263 var _tv2 = getTypoCssVars();
264 if (_tv2) {
265 Object.assign(s, _tv2(a.titleTypo, '--bkia-tt-'));
266 Object.assign(s, _tv2(a.subtitleTypo, '--bkia-st-'));
267 Object.assign(s, _tv2(a.labelTypo, '--bkia-lb-'));
268 }
269 return s;
270 })() });
271 var isHorizontal = a.direction === 'horizontal';
272
273 return el(Fragment, null,
274 el(InspectorControls, null,
275
276 /* — Panels — */
277 el(PanelBody, { title: __('Panels', 'blockenberg') + ' (' + a.panels.length + ')', initialOpen: true },
278 a.panels.map(function (panel, idx) {
279 return el(PanelEditor, { key: idx, panel: panel, idx: idx, onUpdate: updatePanel, onRemove: removePanel });
280 }),
281 el(Button, { variant: 'primary', onClick: addPanel, style: { width: '100%', marginTop: '8px' } },
282 __('+ Add Panel', 'blockenberg'))
283 ),
284
285 /* — Behaviour — */
286 el(PanelBody, { title: __('Behaviour', 'blockenberg'), initialOpen: false },
287 el(SelectControl, { label: __('Trigger', 'blockenberg'), value: a.trigger, options: triggerOptions, onChange: function (v) { setAttributes({ trigger: v }); } }),
288 el(SelectControl, { label: __('Direction', 'blockenberg'), value: a.direction, options: directionOptions, onChange: function (v) { setAttributes({ direction: v }); } }),
289 el(SelectControl, { label: __('Default Open Panel', 'blockenberg'), value: a.defaultOpen, options: defaultOpenOptions, onChange: function (v) { setAttributes({ defaultOpen: parseInt(v,10) }); } }),
290 el(RangeControl, { label: __('Height (px)', 'blockenberg'), value: a.height, min: 200, max: 900, onChange: function (v) { setAttributes({ height: v }); } }),
291 el(RangeControl, { label: __('Expanded Flex', 'blockenberg'), value: a.expandedFlex, min: 2, max: 10, onChange: function (v) { setAttributes({ expandedFlex: v }); } }),
292 el(RangeControl, { label: __('Collapsed Flex', 'blockenberg'), value: a.collapsedFlex, min: 1, max: 4, onChange: function (v) { setAttributes({ collapsedFlex: v }); } }),
293 el(RangeControl, { label: __('Transition Duration (ms)', 'blockenberg'), value: a.transitionDuration, min: 100, max: 1200, step: 50, onChange: function (v) { setAttributes({ transitionDuration: v }); } }),
294 el(SelectControl, { label: __('Easing', 'blockenberg'), value: a.transitionEasing, options: easingOptions, onChange: function (v) { setAttributes({ transitionEasing: v }); } }),
295 el(ToggleControl, { label: __('Scale Image on Expand', 'blockenberg'), checked: a.scaleOnHover, onChange: function (v) { setAttributes({ scaleOnHover: v }); }, __nextHasNoMarginBottom: true })
296 ),
297
298 /* — Text — */
299 el(PanelBody, { title: __('Text Content', 'blockenberg'), initialOpen: false },
300 el(ToggleControl, { label: __('Show Label', 'blockenberg'), checked: a.showLabel, onChange: function (v) { setAttributes({ showLabel: v }); }, __nextHasNoMarginBottom: true }),
301 el(ToggleControl, { label: __('Show Title', 'blockenberg'), checked: a.showTitle, onChange: function (v) { setAttributes({ showTitle: v }); }, __nextHasNoMarginBottom: true }),
302 el(ToggleControl, { label: __('Show Subtitle', 'blockenberg'), checked: a.showSubtitle, onChange: function (v) { setAttributes({ showSubtitle: v }); }, __nextHasNoMarginBottom: true }),
303 el(ToggleControl, { label: __('Show Arrow', 'blockenberg'), checked: a.showArrow, onChange: function (v) { setAttributes({ showArrow: v }); }, __nextHasNoMarginBottom: true }),
304 el(SelectControl, { label: __('Text Position', 'blockenberg'), value: a.textPosition, options: textPosOptions, onChange: function (v) { setAttributes({ textPosition: v }); } }),
305 el(SelectControl, { label: __('Text Visibility', 'blockenberg'), value: a.textVisibility, options: textVisOptions, onChange: function (v) { setAttributes({ textVisibility: v }); } })
306 ),
307
308 /* — Style — */
309 el(PanelBody, { title: __('Style', 'blockenberg'), initialOpen: false },
310 el(RangeControl, { label: __('Gap (px)', 'blockenberg'), value: a.gap, min: 0, max: 32, onChange: function (v) { setAttributes({ gap: v }); } }),
311 el(RangeControl, { label: __('Border Radius (px)', 'blockenberg'), value: a.borderRadius, min: 0, max: 40, onChange: function (v) { setAttributes({ borderRadius: v }); } }),
312 el(RangeControl, { label: __('Overlay Opacity (collapsed, %)', 'blockenberg'), value: a.overlayOpacity, min: 0, max: 100, onChange: function (v) { setAttributes({ overlayOpacity: v }); } }),
313 el(RangeControl, { label: __('Overlay Opacity (expanded, %)', 'blockenberg'), value: a.overlayHoverOpacity, min: 0, max: 100, onChange: function (v) { setAttributes({ overlayHoverOpacity: v }); } }),
314 el(BkbgColorSwatch, { label: __('Label Background', 'blockenberg'), value: a.labelBg, onChange: function (v) { setAttributes({ labelBg: v }); } })
315 ),
316
317 /* — Colors — */
318
319 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
320 getTypographyControl() && el( getTypographyControl(), { label: __( 'Title', 'blockenberg' ), value: a.titleTypo || {}, onChange: function(v){ setAttributes({ titleTypo: v }); } }),
321 getTypographyControl() && el( getTypographyControl(), { label: __( 'Subtitle', 'blockenberg' ), value: a.subtitleTypo || {}, onChange: function(v){ setAttributes({ subtitleTypo: v }); } }),
322 getTypographyControl() && el( getTypographyControl(), { label: __( 'Label', 'blockenberg' ), value: a.labelTypo || {}, onChange: function(v){ setAttributes({ labelTypo: v }); } })
323 ),
324 el(PanelColorSettings, {
325 title: __('Colors', 'blockenberg'),
326 initialOpen: false,
327 colorSettings: [
328 { label: __('Overlay Color', 'blockenberg'), value: a.overlayColor, onChange: function (v) { setAttributes({ overlayColor: v || '#000000' }); } },
329 { label: __('Title Color', 'blockenberg'), value: a.titleColor, onChange: function (v) { setAttributes({ titleColor: v || '#ffffff' }); } },
330 { label: __('Subtitle Color', 'blockenberg'), value: a.subtitleColor, onChange: function (v) { setAttributes({ subtitleColor: v || '#e5e7eb' }); } },
331 { label: __('Label Color', 'blockenberg'), value: a.labelColor, onChange: function (v) { setAttributes({ labelColor: v || '#ffffff' }); } },
332 { label: __('Background Fallback', 'blockenberg'), value: a.bgFallbackColor, onChange: function (v) { setAttributes({ bgFallbackColor: v || '#374151' }); } },
333 ]
334 })
335 ),
336
337 /* ── Canvas ─────────────────────────────────────────────────── */
338 el('div', blockProps,
339 el('div', {
340 className: 'bkia-container',
341 style: {
342 display: 'flex',
343 flexDirection: isHorizontal ? 'row' : 'column',
344 height: a.height + 'px',
345 gap: a.gap + 'px',
346 overflow: 'hidden',
347 }
348 },
349 a.panels.map(function (panel, idx) {
350 return renderPanel(panel, idx, a, idx === active, true);
351 })
352 )
353 )
354 );
355 },
356
357 save: function (props) {
358 var a = props.attributes;
359 var blockProps = wp.blockEditor.useBlockProps.save({ className: 'bkia-wrap', style: (function(){
360 var s = {};
361 if (a.titleSize) s['--bkia-title-sz'] = a.titleSize + 'px';
362 if (a.subtitleSize) s['--bkia-subtitle-sz'] = a.subtitleSize + 'px';
363 if (a.labelSize) s['--bkia-label-sz'] = a.labelSize + 'px';
364 if (a.titleWeight) s['--bkia-title-w'] = String(a.titleWeight);
365 var _tv2 = getTypoCssVars();
366 if (_tv2) {
367 Object.assign(s, _tv2(a.titleTypo, '--bkia-tt-'));
368 Object.assign(s, _tv2(a.subtitleTypo, '--bkia-st-'));
369 Object.assign(s, _tv2(a.labelTypo, '--bkia-lb-'));
370 }
371 return s;
372 })() });
373 var isHorizontal = a.direction === 'horizontal';
374
375 return el('div', blockProps,
376 el('div', {
377 className: 'bkia-container',
378 'data-trigger': a.trigger,
379 'data-default': a.defaultOpen,
380 'data-direction': a.direction,
381 'data-exp-flex': a.expandedFlex,
382 'data-col-flex': a.collapsedFlex,
383 'data-duration': a.transitionDuration,
384 'data-easing': a.transitionEasing,
385 'data-overlay': a.overlayOpacity,
386 'data-ov-hover': a.overlayHoverOpacity,
387 'data-ov-color': a.overlayColor,
388 'data-scale': a.scaleOnHover ? '1' : '0',
389 'data-text-vis': a.textVisibility,
390 style: {
391 display: 'flex',
392 flexDirection: isHorizontal ? 'row' : 'column',
393 height: a.height + 'px',
394 gap: a.gap + 'px',
395 overflow: 'hidden',
396 }
397 },
398 a.panels.map(function (panel, idx) {
399 return renderPanel(panel, idx, a, idx === a.defaultOpen, false);
400 })
401 )
402 );
403 }
404 });
405 }() );
406