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 / before-after-grid / index.js

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

313 lines 18.3 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 registerBlockType = wp.blocks.registerBlockType;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var MediaUpload = wp.blockEditor.MediaUpload;
8 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
9 var useBlockProps = wp.blockEditor.useBlockProps;
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 function getTypographyControl() {
19 return (typeof window.bkbgTypographyControl !== 'undefined') ? window.bkbgTypographyControl : null;
20 }
21 function getTypoCssVars() {
22 return (typeof window.bkbgTypoCssVars !== 'undefined') ? window.bkbgTypoCssVars : function() { return {}; };
23 }
24
25 var COLUMNS_OPTIONS = [
26 { label: '1 column', value: 1 },
27 { label: '2 columns', value: 2 },
28 { label: '3 columns', value: 3 },
29 ];
30 var ASPECT_OPTIONS = [
31 { label: '16:9 (Widescreen)', value: '16/9' },
32 { label: '4:3 (Standard)', value: '4/3' },
33 { label: '3:2 (Photo)', value: '3/2' },
34 { label: '1:1 (Square)', value: '1/1' },
35 { label: '3:4 (Portrait)', value: '3/4' },
36 ];
37 var HANDLE_STYLE_OPTIONS = [
38 { label: 'Circle + Arrows', value: 'circle-arrows' },
39 { label: 'Circle only', value: 'circle' },
40 { label: 'Line (thin)', value: 'line' },
41 { label: 'Double arrow', value: 'double-arrow' },
42 ];
43 var LABEL_POS_OPTIONS = [
44 { label: 'Inner edge', value: 'inner-edge' },
45 { label: 'Outer corner', value: 'outer-corner' },
46 ];
47 var ORIENTATION_OPTIONS = [
48 { label: 'Horizontal (left/right)', value: 'horizontal' },
49 { label: 'Vertical (top/bottom)', value: 'vertical' },
50 ];
51
52 function updatePair(arr, idx, field, val) {
53 return arr.map(function (p, i) {
54 if (i !== idx) return p;
55 var patch = {}; patch[field] = val;
56 return Object.assign({}, p, patch);
57 });
58 }
59
60 function editorPairPreview(pair, a, i) {
61 var isHoriz = a.orientation !== 'vertical';
62 var aspect = (a.aspect || '4/3').split('/');
63 var ratio = (parseInt(aspect[1], 10) / parseInt(aspect[0], 10)) * 100;
64
65 return el('div', {
66 key: i,
67 style: {
68 borderRadius: a.borderRadius + 'px',
69 overflow: 'hidden',
70 background: '#f1f5f9',
71 boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
72 }
73 },
74 // Image pair
75 el('div', { style: { position: 'relative', paddingTop: ratio + '%' } },
76 // Before image (left half or top half)
77 pair.beforeUrl && el('img', {
78 src: pair.beforeUrl, alt: pair.beforeAlt,
79 style: {
80 position: 'absolute', top: 0,
81 left: 0, width: isHoriz ? '50%' : '100%',
82 height: isHoriz ? '100%' : '50%',
83 objectFit: 'cover',
84 }
85 }),
86 // After image (right half or bottom half)
87 pair.afterUrl && el('img', {
88 src: pair.afterUrl, alt: pair.afterAlt,
89 style: {
90 position: 'absolute', top: isHoriz ? 0 : '50%',
91 left: isHoriz ? '50%' : 0,
92 width: isHoriz ? '50%' : '100%',
93 height: isHoriz ? '100%' : '50%',
94 objectFit: 'cover',
95 }
96 }),
97 // Divider line
98 el('div', {
99 style: {
100 position: 'absolute',
101 top: isHoriz ? 0 : '50%',
102 left: isHoriz ? '50%' : 0,
103 width: isHoriz ? a.lineWidth + 'px' : '100%',
104 height: isHoriz ? '100%' : a.lineWidth + 'px',
105 background: a.lineColor,
106 transform: isHoriz ? 'translateX(-50%)' : 'translateY(-50%)',
107 }
108 }),
109 // Labels
110 a.showLabels && el(Fragment, null,
111 el('span', {
112 className: 'bkbg-bag-label bkbg-bag-label-before',
113 style: {
114 position: 'absolute', top: 8, left: 8,
115 background: a.labelBg, color: a.labelColor,
116 padding: '2px 8px', borderRadius: 4,
117 }
118 }, pair.beforeLabel || 'Before'),
119 el('span', {
120 className: 'bkbg-bag-label bkbg-bag-label-after',
121 style: {
122 position: 'absolute', top: 8, right: 8,
123 background: a.labelBg, color: a.labelColor,
124 padding: '2px 8px', borderRadius: 4,
125 }
126 }, pair.afterLabel || 'After')
127 ),
128 // Handle
129 el('div', {
130 style: {
131 position: 'absolute',
132 top: isHoriz ? '50%' : '50%',
133 left: isHoriz ? '50%' : '50%',
134 transform: 'translate(-50%, -50%)',
135 width: a.handleSize + 'px', height: a.handleSize + 'px',
136 background: a.handleBg,
137 borderRadius: '50%',
138 display: 'flex', alignItems: 'center', justifyContent: 'center',
139 boxShadow: '0 2px 8px rgba(0,0,0,0.18)',
140 color: a.handleColor, fontSize: 14, fontWeight: 700,
141 zIndex: 2,
142 }
143 }, '')
144 ),
145 // Caption
146 a.showCaptions && pair.caption && el('div', {
147 className: 'bkbg-bag-caption',
148 style: {
149 padding: '8px 12px',
150 color: a.captionColor, background: a.captionBg,
151 }
152 }, pair.caption)
153 );
154 }
155
156 registerBlockType('blockenberg/before-after-grid', {
157 title: __('Before / After Grid', 'blockenberg'),
158 icon: 'align-pull-left',
159 category: 'bkbg-media',
160 description: __('A grid of draggable before/after image comparison sliders.', 'blockenberg'),
161
162 edit: function (props) {
163 var a = props.attributes;
164 var set = props.setAttributes;
165 var _tv = getTypoCssVars();
166 var TC = getTypographyControl();
167 var bpStyle = {};
168 Object.assign(bpStyle, _tv(a.labelTypo || {}, '--bkbg-bag-label-'));
169 Object.assign(bpStyle, _tv(a.captionTypo || {}, '--bkbg-bag-caption-'));
170 var blockProps = useBlockProps({ style: bpStyle });
171
172 return el(Fragment, null,
173 el(InspectorControls, null,
174
175 // Pairs
176 el(PanelBody, { title: 'Image Pairs (' + a.pairs.length + ')', initialOpen: true },
177 a.pairs.map(function (pair, i) {
178 return el('div', {
179 key: i,
180 style: { border: '1px solid #e5e7eb', borderRadius: 8, padding: 10, marginBottom: 10, background: '#fafafa' }
181 },
182 el('p', { style: { fontWeight: 700, fontSize: 13, margin: '0 0 8px', color: '#374151' } }, 'Pair ' + (i + 1)),
183
184 el('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 8 } },
185 // Before
186 el('div', null,
187 el('p', { style: { fontSize: 11, fontWeight: 600, color: '#6b7280', margin: '0 0 4px' } }, 'BEFORE'),
188 pair.beforeUrl && el('img', { src: pair.beforeUrl, style: { width: '100%', height: 60, objectFit: 'cover', borderRadius: 6, marginBottom: 4 } }),
189 el(MediaUploadCheck, null,
190 el(MediaUpload, {
191 onSelect: function (m) {
192 var u = updatePair(a.pairs, i, 'beforeUrl', m.url);
193 u = updatePair(u, i, 'beforeId', m.id);
194 u = updatePair(u, i, 'beforeAlt', m.alt || 'Before');
195 set({ pairs: u });
196 },
197 allowedTypes: ['image'], value: pair.beforeId,
198 render: function (mp) {
199 return el(Button, { variant: 'secondary', size: 'small', onClick: mp.open, __nextHasNoMarginBottom: true }, pair.beforeUrl ? '' : 'Select');
200 }
201 })
202 ),
203 el('div', { style: { height: 4 } }),
204 el(TextControl, { label: 'Label', value: pair.beforeLabel, onChange: function (v) { set({ pairs: updatePair(a.pairs, i, 'beforeLabel', v) }); }, __nextHasNoMarginBottom: true })
205 ),
206 // After
207 el('div', null,
208 el('p', { style: { fontSize: 11, fontWeight: 600, color: '#6b7280', margin: '0 0 4px' } }, 'AFTER'),
209 pair.afterUrl && el('img', { src: pair.afterUrl, style: { width: '100%', height: 60, objectFit: 'cover', borderRadius: 6, marginBottom: 4 } }),
210 el(MediaUploadCheck, null,
211 el(MediaUpload, {
212 onSelect: function (m) {
213 var u = updatePair(a.pairs, i, 'afterUrl', m.url);
214 u = updatePair(u, i, 'afterId', m.id);
215 u = updatePair(u, i, 'afterAlt', m.alt || 'After');
216 set({ pairs: u });
217 },
218 allowedTypes: ['image'], value: pair.afterId,
219 render: function (mp) {
220 return el(Button, { variant: 'secondary', size: 'small', onClick: mp.open, __nextHasNoMarginBottom: true }, pair.afterUrl ? '' : 'Select');
221 }
222 })
223 ),
224 el('div', { style: { height: 4 } }),
225 el(TextControl, { label: 'Label', value: pair.afterLabel, onChange: function (v) { set({ pairs: updatePair(a.pairs, i, 'afterLabel', v) }); }, __nextHasNoMarginBottom: true })
226 )
227 ),
228 el(TextControl, { label: 'Caption', value: pair.caption, onChange: function (v) { set({ pairs: updatePair(a.pairs, i, 'caption', v) }); }, __nextHasNoMarginBottom: true }),
229 el('div', { style: { height: 4 } }),
230 el(Button, { isDestructive: true, variant: 'link', size: 'small', onClick: function () { set({ pairs: a.pairs.filter(function (_, idx) { return idx !== i; }) }); }, __nextHasNoMarginBottom: true }, 'Remove Pair')
231 );
232 }),
233 el(Button, {
234 variant: 'secondary', onClick: function () {
235 set({ pairs: a.pairs.concat([{ beforeUrl: '', beforeId: 0, beforeAlt: 'Before', beforeLabel: 'Before', afterUrl: '', afterId: 0, afterAlt: 'After', afterLabel: 'After', caption: '' }]) });
236 },
237 style: { width: '100%', justifyContent: 'center' }, __nextHasNoMarginBottom: true
238 }, '+ Add Pair')
239 ),
240
241 // Layout
242 el(PanelBody, { title: 'Layout', initialOpen: false },
243 el(SelectControl, { label: 'Columns', value: a.columns, options: COLUMNS_OPTIONS, onChange: function (v) { set({ columns: parseInt(v, 10) }); }, __nextHasNoMarginBottom: true }),
244 el('div', { style: { height: 10 } }),
245 el(SelectControl, { label: 'Aspect Ratio', value: a.aspect, options: ASPECT_OPTIONS, onChange: function (v) { set({ aspect: v }); }, __nextHasNoMarginBottom: true }),
246 el('div', { style: { height: 10 } }),
247 el(RangeControl, { label: 'Gap (px)', value: a.gap, min: 0, max: 60, onChange: function (v) { set({ gap: v }); }, __nextHasNoMarginBottom: true }),
248 el('div', { style: { height: 10 } }),
249 el(RangeControl, { label: 'Border Radius (px)', value: a.borderRadius, min: 0, max: 32, onChange: function (v) { set({ borderRadius: v }); }, __nextHasNoMarginBottom: true }),
250 el('div', { style: { height: 10 } }),
251 el(SelectControl, { label: 'Slider Orientation', value: a.orientation, options: ORIENTATION_OPTIONS, onChange: function (v) { set({ orientation: v }); }, __nextHasNoMarginBottom: true }),
252 el('div', { style: { height: 10 } }),
253 el(RangeControl, { label: 'Start Position (%)', value: a.startPosition, min: 10, max: 90, onChange: function (v) { set({ startPosition: v }); }, __nextHasNoMarginBottom: true })
254 ),
255
256 // Handle & Labels
257 el(PanelBody, { title: 'Handle & Labels', initialOpen: false },
258 el(SelectControl, { label: 'Handle Style', value: a.handleStyle, options: HANDLE_STYLE_OPTIONS, onChange: function (v) { set({ handleStyle: v }); }, __nextHasNoMarginBottom: true }),
259 el('div', { style: { height: 10 } }),
260 el(RangeControl, { label: 'Handle Size (px)', value: a.handleSize, min: 24, max: 80, onChange: function (v) { set({ handleSize: v }); }, __nextHasNoMarginBottom: true }),
261 el('div', { style: { height: 10 } }),
262 el(RangeControl, { label: 'Line Width (px)', value: a.lineWidth, min: 1, max: 8, onChange: function (v) { set({ lineWidth: v }); }, __nextHasNoMarginBottom: true }),
263 el('div', { style: { height: 10 } }),
264 el(ToggleControl, { label: 'Show labels', checked: a.showLabels, onChange: function (v) { set({ showLabels: v }); }, __nextHasNoMarginBottom: true }),
265 el('div', { style: { height: 10 } }),
266 el(ToggleControl, { label: 'Show captions', checked: a.showCaptions, onChange: function (v) { set({ showCaptions: v }); }, __nextHasNoMarginBottom: true }),
267 ),
268
269 // Colors
270
271 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
272 a.showLabels && TC && el(TC, { label: __('Label', 'blockenberg'), value: a.labelTypo || {}, onChange: function(v){ set({labelTypo: v}); } }),
273 a.showCaptions && TC && el(TC, { label: __('Caption', 'blockenberg'), value: a.captionTypo || {}, onChange: function(v){ set({captionTypo: v}); } })
274 ),
275 el(PanelColorSettings, {
276 title: 'Colors',
277 initialOpen: false,
278 colorSettings: [
279 { label: 'Handle Background', value: a.handleBg, onChange: function (v) { set({ handleBg: v || '#ffffff' }); } },
280 { label: 'Handle Icon', value: a.handleColor, onChange: function (v) { set({ handleColor: v || '#374151' }); } },
281 { label: 'Divider Line', value: a.lineColor, onChange: function (v) { set({ lineColor: v || '#ffffff' }); } },
282 { label: 'Label Background', value: a.labelBg, onChange: function (v) { set({ labelBg: v || 'rgba(0,0,0,0.55)' }); } },
283 { label: 'Label Text', value: a.labelColor, onChange: function (v) { set({ labelColor: v || '#ffffff' }); } },
284 { label: 'Caption Background',value: a.captionBg, onChange: function (v) { set({ captionBg: v || '#f8fafc' }); } },
285 { label: 'Caption Text', value: a.captionColor,onChange: function (v) { set({ captionColor: v || '#374151' }); } },
286 ]
287 })
288 ),
289
290 // ── Editor Preview ──
291 el('div', blockProps,
292 el('p', { style: { fontSize: 11, color: '#9ca3af', margin: '0 0 10px', textAlign: 'center' } }, 'Before/After Grid — drag to compare on the frontend'),
293 el('div', {
294 style: {
295 display: 'grid',
296 gridTemplateColumns: 'repeat(' + a.columns + ', 1fr)',
297 gap: a.gap + 'px',
298 }
299 },
300 a.pairs.map(function (pair, i) { return editorPairPreview(pair, a, i); })
301 )
302 )
303 );
304 },
305
306 save: function (props) {
307 return el('div', useBlockProps.save(),
308 el('div', { className: 'bkbg-bag-app', 'data-opts': JSON.stringify(props.attributes) })
309 );
310 }
311 });
312 }() );
313