# blockenberg/2.0.7/blocks/feature-grid/index.js

Blockenberg — 600+ Advanced Gutenberg Blocks &amp; AI Agent for WordPress Block Editor, version 2.0.7. 423 lines.

- Page: https://pluginprobe.com/plugins/blockenberg/2.0.7/code/blocks/feature-grid/index.js
- Raw: https://pluginprobe.com/plugins/blockenberg/2.0.7/raw/blocks/feature-grid/index.js
- Modified: 2026-03-06T12:32:40+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/blockenberg/2.0.7/code/blocks/feature-grid/index.js#L10-L20`.

```javascript
( function () {
    var el = wp.element.createElement;
    var Fragment = wp.element.Fragment;
    var useState = wp.element.useState;
    var __ = wp.i18n.__;
    var registerBlockType = wp.blocks.registerBlockType;
    var InspectorControls = wp.blockEditor.InspectorControls;
    var useBlockProps = wp.blockEditor.useBlockProps;
    var RichText = wp.blockEditor.RichText;
    var PanelBody    = wp.components.PanelBody;
    var Button       = wp.components.Button;
    var ToggleControl = wp.components.ToggleControl;
    var RangeControl = wp.components.RangeControl;
    var SelectControl = wp.components.SelectControl;
    var TextControl  = wp.components.TextControl;
    var ColorPicker  = wp.components.ColorPicker;
    var Popover      = wp.components.Popover;

    var _fgTC, _fgTV;
    function _tc() { return _fgTC || (_fgTC = window.bkbgTypographyControl); }
    function _tv() { return _fgTV || (_fgTV = window.bkbgTypoCssVars); }

    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'];

    /* ── CSS vars ─────────────────────────────────────────────────────────── */
    function buildWrapStyle(a) {
        var shadow = a.cardShadow === 'sm' ? '0 1px 4px rgba(0,0,0,0.07)' :
                     a.cardShadow === 'md' ? '0 2px 12px rgba(0,0,0,0.1)' :
                     a.cardShadow === 'lg' ? '0 8px 32px rgba(0,0,0,0.13)' : 'none';
        return Object.assign({
            '--bkbg-fg-cols'       : a.columns,
            '--bkbg-fg-cols-tab'   : a.columnsTablet,
            '--bkbg-fg-cols-mob'   : a.columnsMobile,
            '--bkbg-fg-gap'        : a.gap + 'px',
            '--bkbg-fg-pad'        : a.cardPadding + 'px',
            '--bkbg-fg-radius'     : a.borderRadius + 'px',
            '--bkbg-fg-icon-size'  : a.iconSize + 'px',
            '--bkbg-fg-shape-size' : a.iconShapeSize + 'px',
            '--bkbg-fg-icon-color' : a.iconColor,
            '--bkbg-fg-icon-bg'    : a.iconBg,
            '--bkbg-fg-title-color': a.titleColor,
            '--bkbg-fg-text-color' : a.textColor,
            '--bkbg-fg-link-color' : a.linkColor,
            '--bkbg-fg-card-bg'    : a.cardBg,
            '--bkbg-fg-card-border': a.cardBorderColor,
            '--bkbg-fg-shadow'     : shadow,
            '--bkbg-fg-text-align' : a.textAlign
        }, _tv()(a.typoTitle, '--bkbg-fg-tt-'), _tv()(a.typoText, '--bkbg-fg-tx-'), _tv()(a.typoLink, '--bkbg-fg-lk-'));
    }

    /* ── Color picker ─────────────────────────────────────────────────────── */
    function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
        var isOpen = openKey === key;
        return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
            el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
            el('div', { style: { position: 'relative', flexShrink: 0 } },
                el('button', {
                    type: 'button', title: value || 'default',
                    onClick: function () { setOpenKey(isOpen ? null : key); },
                    style: { width: '28px', height: '28px', borderRadius: '4px', border: isOpen ? '2px solid #007cba' : '2px solid #ddd', cursor: 'pointer', padding: 0, background: value || '#cccccc' }
                }),
                isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
                    el('div', { style: { padding: '8px' } },
                        el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
                    )
                )
            )
        );
    }

    /* ── Icon renderer ─────────────────────────────────────────────────────── */
    function renderIcon(item, a) {
        var shapeStyle = {};
        if (a.iconShape !== 'none') {
            shapeStyle = {
                width: a.iconShapeSize + 'px',
                height: a.iconShapeSize + 'px',
                background: a.iconBg,
                borderRadius: a.iconShape === 'circle' ? '50%' : (a.borderRadius + 'px'),
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                flexShrink: 0
            };
        }
        var iconEl;
        if (item.iconType === 'emoji') {
            iconEl = el('span', { style: { fontSize: a.iconSize + 'px', lineHeight: 1, display: 'block' } }, item.iconChar || '⭐');
        } else if (item.iconType === 'dashicon') {
            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' } });
        } else {
            return null;
        }
        return el('div', { className: 'bkbg-fg-icon-wrap', style: a.iconShape !== 'none' ? shapeStyle : { display: 'flex', alignItems: 'center', justifyContent: 'center' } }, iconEl);
    }

    /* ── Register ─────────────────────────────────────────────────────────── */
    registerBlockType('blockenberg/feature-grid', {
        title: __('Feature Grid', 'blockenberg'),
        icon: 'grid-view',
        category: 'bkbg-marketing',
        description: __('Responsive grid of feature cards with icons, titles and text.', 'blockenberg'),

        edit: function (props) {
            var a = props.attributes;
            var setAttributes = props.setAttributes;

            var openColorKeyState = useState(null);
            var openColorKey = openColorKeyState[0];
            var setOpenColorKey = openColorKeyState[1];

            var selectedItemState = useState(0);
            var selectedItem = selectedItemState[0];
            var setSelectedItem = selectedItemState[1];

            function cc(key, label, attrKey) {
                return renderColorControl(key, label, a[attrKey], function (val) {
                    var upd = {}; upd[attrKey] = val; setAttributes(upd);
                }, openColorKey, setOpenColorKey);
            }

            function updateItem(idx, key, val) {
                var updated = (a.items || []).map(function (item, i) {
                    if (i !== idx) return item;
                    var n = Object.assign({}, item); n[key] = val; return n;
                });
                setAttributes({ items: updated });
            }

            function addItem() {
                setAttributes({ items: (a.items || []).concat([{ iconType: 'emoji', iconChar: '✨', iconDashicon: 'star-filled', title: 'New Feature', text: 'Describe this feature here.', linkLabel: '', linkUrl: '', linkTarget: false }]) });
                setSelectedItem((a.items || []).length);
            }

            function removeItem(idx) {
                setAttributes({ items: (a.items || []).filter(function (_, i) { return i !== idx; }) });
                setSelectedItem(Math.max(0, idx - 1));
            }

            function moveItem(idx, dir) {
                var arr = (a.items || []).slice();
                var target = idx + dir;
                if (target < 0 || target >= arr.length) return;
                var tmp = arr[idx]; arr[idx] = arr[target]; arr[target] = tmp;
                setAttributes({ items: arr });
                setSelectedItem(target);
            }

            var blockProps = useBlockProps({
                className: 'bkbg-fg-wrapper bkbg-fg-icon-pos-' + a.iconPosition + ' bkbg-fg-card-' + a.cardStyle + ' bkbg-fg-hover-' + a.hoverEffect,
                style: buildWrapStyle(a)
            });

            var items = a.items || [];
            var curItem = items[selectedItem] || {};

            /* ── Inspector ──────────────────────────────────────────────────── */
            var inspector = el(InspectorControls, {},
                // Items management
                el(PanelBody, { title: __('Items', 'blockenberg'), initialOpen: true },
                    el('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px', marginBottom: '12px' } },
                        items.map(function (item, idx) {
                            var isSelected = idx === selectedItem;
                            return el('div', {
                                key: idx,
                                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' },
                                onClick: function () { setSelectedItem(idx); }
                            },
                                el('span', { style: { fontSize: '18px', minWidth: '24px' } }, item.iconChar || '⭐'),
                                el('span', { style: { flex: 1, fontSize: '12px', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, item.title || __('Untitled', 'blockenberg')),
                                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 } }, '↑'),
                                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 } }, '↓'),
                                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' } }, '×')
                            );
                        })
                    ),
                    el(Button, { variant: 'secondary', onClick: addItem, style: { width: '100%', justifyContent: 'center', marginBottom: '16px' } },
                        '+ ' + __('Add Item', 'blockenberg')
                    ),
                    items.length > 0 && el(Fragment, {},
                        el('div', { style: { borderTop: '1px solid #e0e0e0', paddingTop: '12px' } },
                            el('strong', { style: { fontSize: '12px', display: 'block', marginBottom: '8px' } },
                                __('Edit Item', 'blockenberg') + ' #' + (selectedItem + 1)
                            ),
                            el(SelectControl, {
                                label: __('Icon Type', 'blockenberg'), value: curItem.iconType || 'emoji',
                                options: [
                                    { label: __('Emoji', 'blockenberg'), value: 'emoji' },
                                    { label: __('Dashicon', 'blockenberg'), value: 'dashicon' },
                                    { label: __('None', 'blockenberg'), value: 'none' }
                                ],
                                onChange: function (v) { updateItem(selectedItem, 'iconType', v); }
                            }),
                            curItem.iconType === 'emoji' && el(TextControl, {
                                label: __('Emoji / Icon Character', 'blockenberg'),
                                value: curItem.iconChar || '',
                                onChange: function (v) { updateItem(selectedItem, 'iconChar', v); }
                            }),
                            curItem.iconType === 'dashicon' && el(SelectControl, {
                                label: __('Dashicon', 'blockenberg'),
                                value: curItem.iconDashicon || 'star-filled',
                                options: DASHICONS.map(function (d) { return { label: d, value: d }; }),
                                onChange: function (v) { updateItem(selectedItem, 'iconDashicon', v); }
                            }),
                            el(TextControl, {
                                label: __('Title', 'blockenberg'), value: curItem.title || '',
                                onChange: function (v) { updateItem(selectedItem, 'title', v); }
                            }),
                            el(TextControl, {
                                label: __('Description', 'blockenberg'), value: curItem.text || '',
                                onChange: function (v) { updateItem(selectedItem, 'text', v); }
                            }),
                            el(TextControl, {
                                label: __('Link Label (optional)', 'blockenberg'), value: curItem.linkLabel || '',
                                onChange: function (v) { updateItem(selectedItem, 'linkLabel', v); }
                            }),
                            curItem.linkLabel && el(Fragment, {},
                                el(TextControl, {
                                    label: __('Link URL', 'blockenberg'), value: curItem.linkUrl || '',
                                    onChange: function (v) { updateItem(selectedItem, 'linkUrl', v); }
                                }),
                                el(ToggleControl, {
                                    label: __('Open in new tab', 'blockenberg'), checked: !!curItem.linkTarget,
                                    onChange: function (v) { updateItem(selectedItem, 'linkTarget', v); }
                                })
                            )
                        )
                    )
                ),

                // Layout
                el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: false },
                    el(RangeControl, {
                        label: __('Columns (Desktop)', 'blockenberg'), value: a.columns, min: 1, max: 6,
                        onChange: function (v) { setAttributes({ columns: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Columns (Tablet)', 'blockenberg'), value: a.columnsTablet, min: 1, max: 4,
                        onChange: function (v) { setAttributes({ columnsTablet: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Columns (Mobile)', 'blockenberg'), value: a.columnsMobile, min: 1, max: 3,
                        onChange: function (v) { setAttributes({ columnsMobile: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Gap (px)', 'blockenberg'), value: a.gap, min: 0, max: 80,
                        onChange: function (v) { setAttributes({ gap: v }); }
                    }),
                    el(SelectControl, {
                        label: __('Text Alignment', 'blockenberg'), value: a.textAlign,
                        options: [{ label: __('Left', 'blockenberg'), value: 'left' }, { label: __('Center', 'blockenberg'), value: 'center' }, { label: __('Right', 'blockenberg'), value: 'right' }],
                        onChange: function (v) { setAttributes({ textAlign: v }); }
                    }),
                    el(SelectControl, {
                        label: __('Icon Position', 'blockenberg'), value: a.iconPosition,
                        options: [{ label: __('Top (above title)', 'blockenberg'), value: 'top' }, { label: __('Left (inline)', 'blockenberg'), value: 'left' }],
                        onChange: function (v) { setAttributes({ iconPosition: v }); }
                    })
                ),

                // Card Style
                el(PanelBody, { title: __('Card Style', 'blockenberg'), initialOpen: false },
                    el(SelectControl, {
                        label: __('Card Style', 'blockenberg'), value: a.cardStyle,
                        options: [
                            { label: __('Shadow', 'blockenberg'), value: 'shadow' },
                            { label: __('Outlined', 'blockenberg'), value: 'outlined' },
                            { label: __('Flat', 'blockenberg'), value: 'flat' },
                            { label: __('Plain (no card)', 'blockenberg'), value: 'plain' }
                        ],
                        onChange: function (v) { setAttributes({ cardStyle: v }); }
                    }),
                    a.cardStyle === 'shadow' && el(SelectControl, {
                        label: __('Shadow Size', 'blockenberg'), value: a.cardShadow,
                        options: [{ label: __('Small', 'blockenberg'), value: 'sm' }, { label: __('Medium', 'blockenberg'), value: 'md' }, { label: __('Large', 'blockenberg'), value: 'lg' }],
                        onChange: function (v) { setAttributes({ cardShadow: v }); }
                    }),
                    el(SelectControl, {
                        label: __('Hover Effect', 'blockenberg'), value: a.hoverEffect,
                        options: [
                            { label: __('Lift (translateY)', 'blockenberg'), value: 'lift' },
                            { label: __('Scale', 'blockenberg'), value: 'scale' },
                            { label: __('Glow (accent border)', 'blockenberg'), value: 'glow' },
                            { label: __('None', 'blockenberg'), value: 'none' }
                        ],
                        onChange: function (v) { setAttributes({ hoverEffect: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Card Padding (px)', 'blockenberg'), value: a.cardPadding, min: 8, max: 80,
                        onChange: function (v) { setAttributes({ cardPadding: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Border Radius (px)', 'blockenberg'), value: a.borderRadius, min: 0, max: 40,
                        onChange: function (v) { setAttributes({ borderRadius: v }); }
                    }),
                    el(ToggleControl, {
                        label: __('Animate on Scroll', 'blockenberg'), checked: a.animate,
                        onChange: function (v) { setAttributes({ animate: v }); }
                    })
                ),

                // Icon
                el(PanelBody, { title: __('Icon Style', 'blockenberg'), initialOpen: false },
                    el(SelectControl, {
                        label: __('Icon Shape Background', 'blockenberg'), value: a.iconShape,
                        options: [{ label: __('Circle', 'blockenberg'), value: 'circle' }, { label: __('Square (rounded)', 'blockenberg'), value: 'square' }, { label: __('None', 'blockenberg'), value: 'none' }],
                        onChange: function (v) { setAttributes({ iconShape: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Icon Size (px)', 'blockenberg'), value: a.iconSize, min: 16, max: 80,
                        onChange: function (v) { setAttributes({ iconSize: v }); }
                    }),
                    a.iconShape !== 'none' && el(RangeControl, {
                        label: __('Shape Size (px)', 'blockenberg'), value: a.iconShapeSize, min: 40, max: 120,
                        onChange: function (v) { setAttributes({ iconShapeSize: v }); }
                    }),
                    cc('iconColor', __('Icon Color', 'blockenberg'), 'iconColor'),
                    a.iconShape !== 'none' && cc('iconBg', __('Icon Background', 'blockenberg'), 'iconBg')
                ),

                // Typography & Colors
                el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
                    _tc() && el(_tc(), {
                        label: __('Title Typography', 'blockenberg'),
                        value: a.typoTitle,
                        onChange: function (v) { setAttributes({ typoTitle: v }); }
                    }),
                    _tc() && el(_tc(), {
                        label: __('Text Typography', 'blockenberg'),
                        value: a.typoText,
                        onChange: function (v) { setAttributes({ typoText: v }); }
                    }),
                    _tc() && el(_tc(), {
                        label: __('Link Typography', 'blockenberg'),
                        value: a.typoLink,
                        onChange: function (v) { setAttributes({ typoLink: v }); }
                    })
                ),
                el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
                    cc('titleColor', __('Title Color', 'blockenberg'), 'titleColor'),
                    cc('textColor', __('Text Color', 'blockenberg'), 'textColor'),
                    cc('linkColor', __('Link Color', 'blockenberg'), 'linkColor'),
                    cc('cardBg', __('Card Background', 'blockenberg'), 'cardBg'),
                    a.cardStyle === 'outlined' && cc('cardBorderColor', __('Card Border Color', 'blockenberg'), 'cardBorderColor')
                )
            );

            /* ── Preview ─────────────────────────────────────────────────── */
            return el(Fragment, {},
                inspector,
                el('div', blockProps,
                    el('div', { className: 'bkbg-fg-grid' },
                        items.map(function (item, idx) {
                            return el('div', { key: idx, className: 'bkbg-fg-card' + (a.animate ? ' bkbg-fg-anim' : '') },
                                item.iconType !== 'none' && el('div', {
                                    className: 'bkbg-fg-icon-area'
                                }, renderIcon(item, a)),
                                el('div', { className: 'bkbg-fg-body' },
                                    el(RichText, {
                                        tagName: 'h3', className: 'bkbg-fg-title',
                                        value: item.title, placeholder: __('Feature Title', 'blockenberg'),
                                        onChange: function (v) { updateItem(idx, 'title', v); },
                                        allowedFormats: ['core/bold', 'core/italic']
                                    }),
                                    el(RichText, {
                                        tagName: 'p', className: 'bkbg-fg-text',
                                        value: item.text, placeholder: __('Feature description…', 'blockenberg'),
                                        onChange: function (v) { updateItem(idx, 'text', v); },
                                        allowedFormats: ['core/bold', 'core/italic', 'core/link']
                                    }),
                                    item.linkLabel && el('a', { className: 'bkbg-fg-link', href: item.linkUrl || '#', style: { pointerEvents: 'none' } },
                                        item.linkLabel,
                                        el('span', { className: 'bkbg-fg-link-arrow' }, ' →')
                                    )
                                )
                            );
                        })
                    )
                )
            );
        },

        /* ── Save ───────────────────────────────────────────────────────── */
        save: function (props) {
            var a = props.attributes;
            var items = a.items || [];

            return el('div', wp.blockEditor.useBlockProps.save({
                className: 'bkbg-fg-wrapper bkbg-fg-icon-pos-' + a.iconPosition + ' bkbg-fg-card-' + a.cardStyle + ' bkbg-fg-hover-' + a.hoverEffect,
                style: buildWrapStyle(a)
            }),
                el('div', { className: 'bkbg-fg-grid' },
                    items.map(function (item, idx) {
                        var iconEl = null;
                        if (item.iconType === 'emoji') {
                            iconEl = el('span', { style: { fontSize: a.iconSize + 'px', lineHeight: 1, display: 'block' } }, item.iconChar || '⭐');
                        } else if (item.iconType === 'dashicon') {
                            iconEl = el('span', { className: 'dashicons dashicons-' + (item.iconDashicon || 'star-filled') });
                        }

                        return el('div', {
                            key: idx,
                            className: 'bkbg-fg-card' + (a.animate ? ' bkbg-fg-anim' : '')
                        },
                            item.iconType !== 'none' && iconEl && el('div', { className: 'bkbg-fg-icon-area' },
                                el('div', { className: 'bkbg-fg-icon-wrap' }, iconEl)
                            ),
                            el('div', { className: 'bkbg-fg-body' },
                                el(RichText.Content, { tagName: 'h3', className: 'bkbg-fg-title', value: item.title }),
                                el(RichText.Content, { tagName: 'p', className: 'bkbg-fg-text', value: item.text }),
                                item.linkLabel && el('a', {
                                    className: 'bkbg-fg-link',
                                    href: item.linkUrl || '#',
                                    target: item.linkTarget ? '_blank' : undefined,
                                    rel: item.linkTarget ? 'noopener noreferrer' : undefined
                                }, item.linkLabel, el('span', { className: 'bkbg-fg-link-arrow', 'aria-hidden': 'true' }, ' →'))
                            )
                        );
                    })
                )
            );
        }
    });
}() );

```
