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

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

423 lines 26.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 useState = wp.element.useState;
5 var __ = wp.i18n.__;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var useBlockProps = wp.blockEditor.useBlockProps;
9 var RichText = wp.blockEditor.RichText;
10 var PanelBody = wp.components.PanelBody;
11 var Button = wp.components.Button;
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 ColorPicker = wp.components.ColorPicker;
17 var Popover = wp.components.Popover;
18
19 var _fgTC, _fgTV;
20 function _tc() { return _fgTC || (_fgTC = window.bkbgTypographyControl); }
21 function _tv() { return _fgTV || (_fgTV = window.bkbgTypoCssVars); }
22
23 var DASHICONS = ['star-filled','admin-site','admin-media','admin-appearance','admin-tools','admin-users','admin-network','admin-home','admin-generic','admin-comments','shield','heart','awards','chart-bar','share','editor-code','format-gallery','format-audio','format-video','format-quote','format-image','format-aside','format-status','format-chat','camera','images-alt2','video-alt3','media-audio','media-code','media-spreadsheet','media-text','media-interactive','media-archive','media-default','media-video','media-document'];
24
25 /* ── CSS vars ─────────────────────────────────────────────────────────── */
26 function buildWrapStyle(a) {
27 var shadow = a.cardShadow === 'sm' ? '0 1px 4px rgba(0,0,0,0.07)' :
28 a.cardShadow === 'md' ? '0 2px 12px rgba(0,0,0,0.1)' :
29 a.cardShadow === 'lg' ? '0 8px 32px rgba(0,0,0,0.13)' : 'none';
30 return Object.assign({
31 '--bkbg-fg-cols' : a.columns,
32 '--bkbg-fg-cols-tab' : a.columnsTablet,
33 '--bkbg-fg-cols-mob' : a.columnsMobile,
34 '--bkbg-fg-gap' : a.gap + 'px',
35 '--bkbg-fg-pad' : a.cardPadding + 'px',
36 '--bkbg-fg-radius' : a.borderRadius + 'px',
37 '--bkbg-fg-icon-size' : a.iconSize + 'px',
38 '--bkbg-fg-shape-size' : a.iconShapeSize + 'px',
39 '--bkbg-fg-icon-color' : a.iconColor,
40 '--bkbg-fg-icon-bg' : a.iconBg,
41 '--bkbg-fg-title-color': a.titleColor,
42 '--bkbg-fg-text-color' : a.textColor,
43 '--bkbg-fg-link-color' : a.linkColor,
44 '--bkbg-fg-card-bg' : a.cardBg,
45 '--bkbg-fg-card-border': a.cardBorderColor,
46 '--bkbg-fg-shadow' : shadow,
47 '--bkbg-fg-text-align' : a.textAlign
48 }, _tv()(a.typoTitle, '--bkbg-fg-tt-'), _tv()(a.typoText, '--bkbg-fg-tx-'), _tv()(a.typoLink, '--bkbg-fg-lk-'));
49 }
50
51 /* ── Color picker ─────────────────────────────────────────────────────── */
52 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
53 var isOpen = openKey === key;
54 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
55 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
56 el('div', { style: { position: 'relative', flexShrink: 0 } },
57 el('button', {
58 type: 'button', title: value || 'default',
59 onClick: function () { setOpenKey(isOpen ? null : key); },
60 style: { width: '28px', height: '28px', borderRadius: '4px', border: isOpen ? '2px solid #007cba' : '2px solid #ddd', cursor: 'pointer', padding: 0, background: value || '#cccccc' }
61 }),
62 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
63 el('div', { style: { padding: '8px' } },
64 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
65 )
66 )
67 )
68 );
69 }
70
71 /* ── Icon renderer ─────────────────────────────────────────────────────── */
72 function renderIcon(item, a) {
73 var shapeStyle = {};
74 if (a.iconShape !== 'none') {
75 shapeStyle = {
76 width: a.iconShapeSize + 'px',
77 height: a.iconShapeSize + 'px',
78 background: a.iconBg,
79 borderRadius: a.iconShape === 'circle' ? '50%' : (a.borderRadius + 'px'),
80 display: 'flex', alignItems: 'center', justifyContent: 'center',
81 flexShrink: 0
82 };
83 }
84 var iconEl;
85 if (item.iconType === 'emoji') {
86 iconEl = el('span', { style: { fontSize: a.iconSize + 'px', lineHeight: 1, display: 'block' } }, item.iconChar || '');
87 } else if (item.iconType === 'dashicon') {
88 iconEl = el('span', { className: 'dashicons dashicons-' + (item.iconDashicon || 'star-filled'), style: { fontSize: a.iconSize + 'px', color: a.iconColor, display: 'block', width: a.iconSize + 'px', height: a.iconSize + 'px' } });
89 } else {
90 return null;
91 }
92 return el('div', { className: 'bkbg-fg-icon-wrap', style: a.iconShape !== 'none' ? shapeStyle : { display: 'flex', alignItems: 'center', justifyContent: 'center' } }, iconEl);
93 }
94
95 /* ── Register ─────────────────────────────────────────────────────────── */
96 registerBlockType('blockenberg/feature-grid', {
97 title: __('Feature Grid', 'blockenberg'),
98 icon: 'grid-view',
99 category: 'bkbg-marketing',
100 description: __('Responsive grid of feature cards with icons, titles and text.', 'blockenberg'),
101
102 edit: function (props) {
103 var a = props.attributes;
104 var setAttributes = props.setAttributes;
105
106 var openColorKeyState = useState(null);
107 var openColorKey = openColorKeyState[0];
108 var setOpenColorKey = openColorKeyState[1];
109
110 var selectedItemState = useState(0);
111 var selectedItem = selectedItemState[0];
112 var setSelectedItem = selectedItemState[1];
113
114 function cc(key, label, attrKey) {
115 return renderColorControl(key, label, a[attrKey], function (val) {
116 var upd = {}; upd[attrKey] = val; setAttributes(upd);
117 }, openColorKey, setOpenColorKey);
118 }
119
120 function updateItem(idx, key, val) {
121 var updated = (a.items || []).map(function (item, i) {
122 if (i !== idx) return item;
123 var n = Object.assign({}, item); n[key] = val; return n;
124 });
125 setAttributes({ items: updated });
126 }
127
128 function addItem() {
129 setAttributes({ items: (a.items || []).concat([{ iconType: 'emoji', iconChar: '', iconDashicon: 'star-filled', title: 'New Feature', text: 'Describe this feature here.', linkLabel: '', linkUrl: '', linkTarget: false }]) });
130 setSelectedItem((a.items || []).length);
131 }
132
133 function removeItem(idx) {
134 setAttributes({ items: (a.items || []).filter(function (_, i) { return i !== idx; }) });
135 setSelectedItem(Math.max(0, idx - 1));
136 }
137
138 function moveItem(idx, dir) {
139 var arr = (a.items || []).slice();
140 var target = idx + dir;
141 if (target < 0 || target >= arr.length) return;
142 var tmp = arr[idx]; arr[idx] = arr[target]; arr[target] = tmp;
143 setAttributes({ items: arr });
144 setSelectedItem(target);
145 }
146
147 var blockProps = useBlockProps({
148 className: 'bkbg-fg-wrapper bkbg-fg-icon-pos-' + a.iconPosition + ' bkbg-fg-card-' + a.cardStyle + ' bkbg-fg-hover-' + a.hoverEffect,
149 style: buildWrapStyle(a)
150 });
151
152 var items = a.items || [];
153 var curItem = items[selectedItem] || {};
154
155 /* ── Inspector ──────────────────────────────────────────────────── */
156 var inspector = el(InspectorControls, {},
157 // Items management
158 el(PanelBody, { title: __('Items', 'blockenberg'), initialOpen: true },
159 el('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px', marginBottom: '12px' } },
160 items.map(function (item, idx) {
161 var isSelected = idx === selectedItem;
162 return el('div', {
163 key: idx,
164 style: { display: 'flex', alignItems: 'center', gap: '6px', background: isSelected ? '#e8f0fe' : '#f5f5f5', border: isSelected ? '1px solid #4285f4' : '1px solid #ddd', borderRadius: '6px', padding: '6px 8px', cursor: 'pointer' },
165 onClick: function () { setSelectedItem(idx); }
166 },
167 el('span', { style: { fontSize: '18px', minWidth: '24px' } }, item.iconChar || ''),
168 el('span', { style: { flex: 1, fontSize: '12px', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, item.title || __('Untitled', 'blockenberg')),
169 el('button', { type: 'button', onClick: function (e) { e.stopPropagation(); moveItem(idx, -1); }, disabled: idx === 0, style: { background: 'none', border: 'none', cursor: 'pointer', fontSize: '14px', padding: '0 2px', opacity: idx === 0 ? 0.3 : 1 } }, ''),
170 el('button', { type: 'button', onClick: function (e) { e.stopPropagation(); moveItem(idx, 1); }, disabled: idx === items.length - 1, style: { background: 'none', border: 'none', cursor: 'pointer', fontSize: '14px', padding: '0 2px', opacity: idx === items.length - 1 ? 0.3 : 1 } }, ''),
171 el('button', { type: 'button', onClick: function (e) { e.stopPropagation(); removeItem(idx); }, style: { background: 'none', border: 'none', cursor: 'pointer', fontSize: '16px', color: '#cc1818', padding: '0 2px' } }, '×')
172 );
173 })
174 ),
175 el(Button, { variant: 'secondary', onClick: addItem, style: { width: '100%', justifyContent: 'center', marginBottom: '16px' } },
176 '+ ' + __('Add Item', 'blockenberg')
177 ),
178 items.length > 0 && el(Fragment, {},
179 el('div', { style: { borderTop: '1px solid #e0e0e0', paddingTop: '12px' } },
180 el('strong', { style: { fontSize: '12px', display: 'block', marginBottom: '8px' } },
181 __('Edit Item', 'blockenberg') + ' #' + (selectedItem + 1)
182 ),
183 el(SelectControl, {
184 label: __('Icon Type', 'blockenberg'), value: curItem.iconType || 'emoji',
185 options: [
186 { label: __('Emoji', 'blockenberg'), value: 'emoji' },
187 { label: __('Dashicon', 'blockenberg'), value: 'dashicon' },
188 { label: __('None', 'blockenberg'), value: 'none' }
189 ],
190 onChange: function (v) { updateItem(selectedItem, 'iconType', v); }
191 }),
192 curItem.iconType === 'emoji' && el(TextControl, {
193 label: __('Emoji / Icon Character', 'blockenberg'),
194 value: curItem.iconChar || '',
195 onChange: function (v) { updateItem(selectedItem, 'iconChar', v); }
196 }),
197 curItem.iconType === 'dashicon' && el(SelectControl, {
198 label: __('Dashicon', 'blockenberg'),
199 value: curItem.iconDashicon || 'star-filled',
200 options: DASHICONS.map(function (d) { return { label: d, value: d }; }),
201 onChange: function (v) { updateItem(selectedItem, 'iconDashicon', v); }
202 }),
203 el(TextControl, {
204 label: __('Title', 'blockenberg'), value: curItem.title || '',
205 onChange: function (v) { updateItem(selectedItem, 'title', v); }
206 }),
207 el(TextControl, {
208 label: __('Description', 'blockenberg'), value: curItem.text || '',
209 onChange: function (v) { updateItem(selectedItem, 'text', v); }
210 }),
211 el(TextControl, {
212 label: __('Link Label (optional)', 'blockenberg'), value: curItem.linkLabel || '',
213 onChange: function (v) { updateItem(selectedItem, 'linkLabel', v); }
214 }),
215 curItem.linkLabel && el(Fragment, {},
216 el(TextControl, {
217 label: __('Link URL', 'blockenberg'), value: curItem.linkUrl || '',
218 onChange: function (v) { updateItem(selectedItem, 'linkUrl', v); }
219 }),
220 el(ToggleControl, {
221 label: __('Open in new tab', 'blockenberg'), checked: !!curItem.linkTarget,
222 onChange: function (v) { updateItem(selectedItem, 'linkTarget', v); }
223 })
224 )
225 )
226 )
227 ),
228
229 // Layout
230 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: false },
231 el(RangeControl, {
232 label: __('Columns (Desktop)', 'blockenberg'), value: a.columns, min: 1, max: 6,
233 onChange: function (v) { setAttributes({ columns: v }); }
234 }),
235 el(RangeControl, {
236 label: __('Columns (Tablet)', 'blockenberg'), value: a.columnsTablet, min: 1, max: 4,
237 onChange: function (v) { setAttributes({ columnsTablet: v }); }
238 }),
239 el(RangeControl, {
240 label: __('Columns (Mobile)', 'blockenberg'), value: a.columnsMobile, min: 1, max: 3,
241 onChange: function (v) { setAttributes({ columnsMobile: v }); }
242 }),
243 el(RangeControl, {
244 label: __('Gap (px)', 'blockenberg'), value: a.gap, min: 0, max: 80,
245 onChange: function (v) { setAttributes({ gap: v }); }
246 }),
247 el(SelectControl, {
248 label: __('Text Alignment', 'blockenberg'), value: a.textAlign,
249 options: [{ label: __('Left', 'blockenberg'), value: 'left' }, { label: __('Center', 'blockenberg'), value: 'center' }, { label: __('Right', 'blockenberg'), value: 'right' }],
250 onChange: function (v) { setAttributes({ textAlign: v }); }
251 }),
252 el(SelectControl, {
253 label: __('Icon Position', 'blockenberg'), value: a.iconPosition,
254 options: [{ label: __('Top (above title)', 'blockenberg'), value: 'top' }, { label: __('Left (inline)', 'blockenberg'), value: 'left' }],
255 onChange: function (v) { setAttributes({ iconPosition: v }); }
256 })
257 ),
258
259 // Card Style
260 el(PanelBody, { title: __('Card Style', 'blockenberg'), initialOpen: false },
261 el(SelectControl, {
262 label: __('Card Style', 'blockenberg'), value: a.cardStyle,
263 options: [
264 { label: __('Shadow', 'blockenberg'), value: 'shadow' },
265 { label: __('Outlined', 'blockenberg'), value: 'outlined' },
266 { label: __('Flat', 'blockenberg'), value: 'flat' },
267 { label: __('Plain (no card)', 'blockenberg'), value: 'plain' }
268 ],
269 onChange: function (v) { setAttributes({ cardStyle: v }); }
270 }),
271 a.cardStyle === 'shadow' && el(SelectControl, {
272 label: __('Shadow Size', 'blockenberg'), value: a.cardShadow,
273 options: [{ label: __('Small', 'blockenberg'), value: 'sm' }, { label: __('Medium', 'blockenberg'), value: 'md' }, { label: __('Large', 'blockenberg'), value: 'lg' }],
274 onChange: function (v) { setAttributes({ cardShadow: v }); }
275 }),
276 el(SelectControl, {
277 label: __('Hover Effect', 'blockenberg'), value: a.hoverEffect,
278 options: [
279 { label: __('Lift (translateY)', 'blockenberg'), value: 'lift' },
280 { label: __('Scale', 'blockenberg'), value: 'scale' },
281 { label: __('Glow (accent border)', 'blockenberg'), value: 'glow' },
282 { label: __('None', 'blockenberg'), value: 'none' }
283 ],
284 onChange: function (v) { setAttributes({ hoverEffect: v }); }
285 }),
286 el(RangeControl, {
287 label: __('Card Padding (px)', 'blockenberg'), value: a.cardPadding, min: 8, max: 80,
288 onChange: function (v) { setAttributes({ cardPadding: v }); }
289 }),
290 el(RangeControl, {
291 label: __('Border Radius (px)', 'blockenberg'), value: a.borderRadius, min: 0, max: 40,
292 onChange: function (v) { setAttributes({ borderRadius: v }); }
293 }),
294 el(ToggleControl, {
295 label: __('Animate on Scroll', 'blockenberg'), checked: a.animate,
296 onChange: function (v) { setAttributes({ animate: v }); }
297 })
298 ),
299
300 // Icon
301 el(PanelBody, { title: __('Icon Style', 'blockenberg'), initialOpen: false },
302 el(SelectControl, {
303 label: __('Icon Shape Background', 'blockenberg'), value: a.iconShape,
304 options: [{ label: __('Circle', 'blockenberg'), value: 'circle' }, { label: __('Square (rounded)', 'blockenberg'), value: 'square' }, { label: __('None', 'blockenberg'), value: 'none' }],
305 onChange: function (v) { setAttributes({ iconShape: v }); }
306 }),
307 el(RangeControl, {
308 label: __('Icon Size (px)', 'blockenberg'), value: a.iconSize, min: 16, max: 80,
309 onChange: function (v) { setAttributes({ iconSize: v }); }
310 }),
311 a.iconShape !== 'none' && el(RangeControl, {
312 label: __('Shape Size (px)', 'blockenberg'), value: a.iconShapeSize, min: 40, max: 120,
313 onChange: function (v) { setAttributes({ iconShapeSize: v }); }
314 }),
315 cc('iconColor', __('Icon Color', 'blockenberg'), 'iconColor'),
316 a.iconShape !== 'none' && cc('iconBg', __('Icon Background', 'blockenberg'), 'iconBg')
317 ),
318
319 // Typography & Colors
320 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
321 _tc() && el(_tc(), {
322 label: __('Title Typography', 'blockenberg'),
323 value: a.typoTitle,
324 onChange: function (v) { setAttributes({ typoTitle: v }); }
325 }),
326 _tc() && el(_tc(), {
327 label: __('Text Typography', 'blockenberg'),
328 value: a.typoText,
329 onChange: function (v) { setAttributes({ typoText: v }); }
330 }),
331 _tc() && el(_tc(), {
332 label: __('Link Typography', 'blockenberg'),
333 value: a.typoLink,
334 onChange: function (v) { setAttributes({ typoLink: v }); }
335 })
336 ),
337 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
338 cc('titleColor', __('Title Color', 'blockenberg'), 'titleColor'),
339 cc('textColor', __('Text Color', 'blockenberg'), 'textColor'),
340 cc('linkColor', __('Link Color', 'blockenberg'), 'linkColor'),
341 cc('cardBg', __('Card Background', 'blockenberg'), 'cardBg'),
342 a.cardStyle === 'outlined' && cc('cardBorderColor', __('Card Border Color', 'blockenberg'), 'cardBorderColor')
343 )
344 );
345
346 /* ── Preview ─────────────────────────────────────────────────── */
347 return el(Fragment, {},
348 inspector,
349 el('div', blockProps,
350 el('div', { className: 'bkbg-fg-grid' },
351 items.map(function (item, idx) {
352 return el('div', { key: idx, className: 'bkbg-fg-card' + (a.animate ? ' bkbg-fg-anim' : '') },
353 item.iconType !== 'none' && el('div', {
354 className: 'bkbg-fg-icon-area'
355 }, renderIcon(item, a)),
356 el('div', { className: 'bkbg-fg-body' },
357 el(RichText, {
358 tagName: 'h3', className: 'bkbg-fg-title',
359 value: item.title, placeholder: __('Feature Title', 'blockenberg'),
360 onChange: function (v) { updateItem(idx, 'title', v); },
361 allowedFormats: ['core/bold', 'core/italic']
362 }),
363 el(RichText, {
364 tagName: 'p', className: 'bkbg-fg-text',
365 value: item.text, placeholder: __('Feature description…', 'blockenberg'),
366 onChange: function (v) { updateItem(idx, 'text', v); },
367 allowedFormats: ['core/bold', 'core/italic', 'core/link']
368 }),
369 item.linkLabel && el('a', { className: 'bkbg-fg-link', href: item.linkUrl || '#', style: { pointerEvents: 'none' } },
370 item.linkLabel,
371 el('span', { className: 'bkbg-fg-link-arrow' }, '')
372 )
373 )
374 );
375 })
376 )
377 )
378 );
379 },
380
381 /* ── Save ───────────────────────────────────────────────────────── */
382 save: function (props) {
383 var a = props.attributes;
384 var items = a.items || [];
385
386 return el('div', wp.blockEditor.useBlockProps.save({
387 className: 'bkbg-fg-wrapper bkbg-fg-icon-pos-' + a.iconPosition + ' bkbg-fg-card-' + a.cardStyle + ' bkbg-fg-hover-' + a.hoverEffect,
388 style: buildWrapStyle(a)
389 }),
390 el('div', { className: 'bkbg-fg-grid' },
391 items.map(function (item, idx) {
392 var iconEl = null;
393 if (item.iconType === 'emoji') {
394 iconEl = el('span', { style: { fontSize: a.iconSize + 'px', lineHeight: 1, display: 'block' } }, item.iconChar || '');
395 } else if (item.iconType === 'dashicon') {
396 iconEl = el('span', { className: 'dashicons dashicons-' + (item.iconDashicon || 'star-filled') });
397 }
398
399 return el('div', {
400 key: idx,
401 className: 'bkbg-fg-card' + (a.animate ? ' bkbg-fg-anim' : '')
402 },
403 item.iconType !== 'none' && iconEl && el('div', { className: 'bkbg-fg-icon-area' },
404 el('div', { className: 'bkbg-fg-icon-wrap' }, iconEl)
405 ),
406 el('div', { className: 'bkbg-fg-body' },
407 el(RichText.Content, { tagName: 'h3', className: 'bkbg-fg-title', value: item.title }),
408 el(RichText.Content, { tagName: 'p', className: 'bkbg-fg-text', value: item.text }),
409 item.linkLabel && el('a', {
410 className: 'bkbg-fg-link',
411 href: item.linkUrl || '#',
412 target: item.linkTarget ? '_blank' : undefined,
413 rel: item.linkTarget ? 'noopener noreferrer' : undefined
414 }, item.linkLabel, el('span', { className: 'bkbg-fg-link-arrow', 'aria-hidden': 'true' }, ''))
415 )
416 );
417 })
418 )
419 );
420 }
421 });
422 }() );
423