# blockenberg/2.0.10/blocks/post-grid/index.js

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

- Page: https://pluginprobe.com/plugins/blockenberg/2.0.10/code/blocks/post-grid/index.js
- Raw: https://pluginprobe.com/plugins/blockenberg/2.0.10/raw/blocks/post-grid/index.js
- Modified: 2026-05-25T23:06:36+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.10/code/blocks/post-grid/index.js#L10-L20`.

```javascript
( function () {
    var el = wp.element.createElement;
    var Fragment = wp.element.Fragment;
    var __ = wp.i18n.__;
    var useState = wp.element.useState;
    var useEffect = wp.element.useEffect;
    var registerBlockType = wp.blocks.registerBlockType;
    var InspectorControls = wp.blockEditor.InspectorControls;
    var useBlockProps = wp.blockEditor.useBlockProps;
    var PanelBody = wp.components.PanelBody;
    var PanelColorSettings = wp.blockEditor.PanelColorSettings;
    var ToggleControl = wp.components.ToggleControl;
    var RangeControl = wp.components.RangeControl;
    var SelectControl = wp.components.SelectControl;
    var TextControl = wp.components.TextControl;
    var Spinner = wp.components.Spinner;
    var Notice = wp.components.Notice;

    var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
    var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }

    var cardStyleOptions = [
        { label: 'Shadow',   value: 'shadow' },
        { label: 'Outlined', value: 'outlined' },
        { label: 'Flat',     value: 'flat' },
        { label: 'Elevated', value: 'elevated' }
    ];

    var hoverEffectOptions = [
        { label: 'Zoom',  value: 'zoom' },
        { label: 'Fade',  value: 'fade' },
        { label: 'Lift',  value: 'lift' },
        { label: 'None',  value: 'none' }
    ];

    var orderbyOptions = [
        { label: 'Date',          value: 'date' },
        { label: 'Title',         value: 'title' },
        { label: 'Menu Order',    value: 'menu_order' },
        { label: 'Modified Date', value: 'modified' },
        { label: 'Random',        value: 'rand' }
    ];

    var readMoreStyleOptions = [
        { label: 'Text Link', value: 'link' },
        { label: 'Button',    value: 'button' },
        { label: 'Arrow →',   value: 'arrow' }
    ];

    var imageRatioOptions = [
        { label: '16:9 (56.25%)', value: '56.25' },
        { label: '4:3  (75%)',    value: '75' },
        { label: '1:1  (100%)',   value: '100' },
        { label: '3:2  (66.67%)',value: '66.67' },
        { label: '21:9 (42.86%)',value: '42.86' }
    ];

    /* ─── Card template (used in editor preview & could be reused) ─── */
    function renderCard(post, attrs) {
        var a = attrs;
        var bodyParts = [];
        if (a.showMeta && post.meta) {
            bodyParts.push(el('div', { className: 'bkbg-pg-meta', key: 'meta' }, post.meta));
        }
        bodyParts.push(
            el('h3', { className: 'bkbg-pg-title', key: 'title' },
                el('a', { href: post.link }, post.title)
            )
        );
        if (a.showExcerpt && post.excerpt) {
            bodyParts.push(el('p', { className: 'bkbg-pg-excerpt', key: 'exc' }, post.excerpt));
        }
        if (a.showReadMore) {
            bodyParts.push(
                el('a', {
                    href: post.link,
                    className: 'bkbg-pg-readmore bkbg-pg-readmore--' + a.readMoreStyle,
                    key: 'rm'
                }, a.readMoreLabel + (a.readMoreStyle === 'arrow' ? ' →' : ''))
            );
        }

        return el('article', { className: 'bkbg-pg-card', key: post.id },
            a.showImage && post.image
                ? el('div', { className: 'bkbg-pg-thumb bkbg-pg-hover--' + a.imageHoverEffect },
                    el('div', { className: 'bkbg-pg-thumb-inner' },
                        el('img', { src: post.image, alt: post.title, loading: 'lazy' })
                    )
                  )
                : null,
            el('div', { className: 'bkbg-pg-body' }, ...bodyParts)
        );
    }

    registerBlockType('blockenberg/post-grid', {
        title: __('Post Grid', 'blockenberg'),
        description: __('Display posts or CPT in a responsive card grid.', 'blockenberg'),
        category: 'bkbg-blog',
        icon: el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
            el('path', { d: 'M3 3h8v8H3V3zm0 10h8v8H3v-8zm10-10h8v8h-8V3zm0 10h8v8h-8v-8z' })
        ),
        attributes: {
            postType:         { type: 'string',  default: 'post' },
            orderby:          { type: 'string',  default: 'date' },
            order:            { type: 'string',  default: 'desc' },
            perPage:          { type: 'number',  default: 6 },
            offset:           { type: 'number',  default: 0 },
            excerptLen:       { type: 'number',  default: 20 },
            columns:          { type: 'number',  default: 3 },
            columnsTablet:    { type: 'number',  default: 2 },
            columnsMobile:    { type: 'number',  default: 1 },
            gapX:             { type: 'number',  default: 24 },
            gapY:             { type: 'number',  default: 24 },
            showImage:        { type: 'boolean', default: true },
            imageRatio:       { type: 'string',  default: '56.25' },
            imageHoverEffect: { type: 'string',  default: 'zoom' },
            showMeta:         { type: 'boolean', default: true },
            showExcerpt:      { type: 'boolean', default: true },
            showReadMore:     { type: 'boolean', default: true },
            readMoreLabel:    { type: 'string',  default: 'Read More' },
            readMoreStyle:    { type: 'string',  default: 'link' },
            cardStyle:        { type: 'string',  default: 'shadow' },
            titleTypo:        { type: 'object',  default: {} },
            excerptTypo:      { type: 'object',  default: {} },
            metaTypo:         { type: 'object',  default: {} },
            cardBg:           { type: 'string',  default: '#ffffff' },
            cardRadius:       { type: 'number',  default: 10 },
            titleColor:       { type: 'string',  default: '#111827' },
            titleHoverColor:  { type: 'string',  default: '#2563eb' },
            excerptColor:     { type: 'string',  default: '#6b7280' },
            metaColor:        { type: 'string',  default: '#9ca3af' },
            linkColor:        { type: 'string',  default: '#2563eb' },
            borderColor:      { type: 'string',  default: '#e5e7eb' },
            noPostsMessage:   { type: 'string',  default: 'No posts found.' }
        },

        deprecated: [{
            attributes: {
                postType:         { type: 'string',  default: 'post' },
                orderby:          { type: 'string',  default: 'date' },
                order:            { type: 'string',  default: 'desc' },
                perPage:          { type: 'number',  default: 6 },
                offset:           { type: 'number',  default: 0 },
                excerptLen:       { type: 'number',  default: 20 },
                columns:          { type: 'number',  default: 3 },
                columnsTablet:    { type: 'number',  default: 2 },
                columnsMobile:    { type: 'number',  default: 1 },
                gapX:             { type: 'number',  default: 24 },
                gapY:             { type: 'number',  default: 24 },
                showImage:        { type: 'boolean', default: true },
                imageRatio:       { type: 'string',  default: '56.25' },
                imageHoverEffect: { type: 'string',  default: 'zoom' },
                showMeta:         { type: 'boolean', default: true },
                showExcerpt:      { type: 'boolean', default: true },
                showReadMore:     { type: 'boolean', default: true },
                readMoreLabel:    { type: 'string',  default: 'Read More' },
                readMoreStyle:    { type: 'string',  default: 'link' },
                cardStyle:        { type: 'string',  default: 'shadow' },
                titleSize:        { type: 'number',  default: 18 },
                titleWeight:      { type: 'number',  default: 700 },
                excerptSize:      { type: 'number',  default: 14 },
                metaSize:         { type: 'number',  default: 12 },
                cardBg:           { type: 'string',  default: '#ffffff' },
                cardRadius:       { type: 'number',  default: 10 },
                titleColor:       { type: 'string',  default: '#111827' },
                titleHoverColor:  { type: 'string',  default: '#2563eb' },
                excerptColor:     { type: 'string',  default: '#6b7280' },
                metaColor:        { type: 'string',  default: '#9ca3af' },
                linkColor:        { type: 'string',  default: '#2563eb' },
                borderColor:      { type: 'string',  default: '#e5e7eb' },
                noPostsMessage:   { type: 'string',  default: 'No posts found.' },
                titleLineHeight:  { type: 'number',  default: 1.3 },
                excerptFontWeight:{ type: 'string',  default: '400' }
            },
            save: function (props) {
                var a = props.attributes;
                var blockProps = wp.blockEditor.useBlockProps.save({
                    className: 'bkbg-pg-wrap bkbg-pg-card--' + a.cardStyle,
                    'data-post-type':   a.postType,
                    'data-orderby':     a.orderby,
                    'data-order':       a.order,
                    'data-per-page':    a.perPage,
                    'data-offset':      a.offset,
                    'data-excerpt-len': a.excerptLen,
                    'data-show-image':  a.showImage ? '1' : '0',
                    'data-show-meta':   a.showMeta  ? '1' : '0',
                    'data-show-excerpt':a.showExcerpt ? '1' : '0',
                    'data-show-rm':     a.showReadMore ? '1' : '0',
                    'data-rm-label':    a.readMoreLabel,
                    'data-rm-style':    a.readMoreStyle,
                    'data-no-posts':    a.noPostsMessage,
                    'data-hover':       a.imageHoverEffect,
                    style: {
                        '--bkbg-pg-cols':        a.columns,
                        '--bkbg-pg-cols-tab':    a.columnsTablet,
                        '--bkbg-pg-cols-mob':    a.columnsMobile,
                        '--bkbg-pg-gap-x':       a.gapX + 'px',
                        '--bkbg-pg-gap-y':       a.gapY + 'px',
                        '--bkbg-pg-ratio':       a.imageRatio + '%',
                        '--bkbg-pg-card-bg':     a.cardBg,
                        '--bkbg-pg-card-radius': a.cardRadius + 'px',
                        '--bkbg-pg-title-size':  a.titleSize + 'px',
                        '--bkbg-pg-title-weight':a.titleWeight,
                        '--bkbg-pg-exc-size':    a.excerptSize + 'px',
                        '--bkbg-pg-meta-size':   a.metaSize + 'px',
                        '--bkbg-pg-title-color': a.titleColor,
                        '--bkbg-pg-title-hover': a.titleHoverColor,
                        '--bkbg-pg-exc-color':   a.excerptColor,
                        '--bkbg-pg-meta-color':  a.metaColor,
                        '--bkbg-pg-link-color':  a.linkColor,
                        '--bkbg-pg-border':      a.borderColor
                    }
                });
                return el('div', blockProps,
                    el('div', { className: 'bkbg-pg-grid' })
                );
            }
        }],

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

            var _state = useState({ posts: [], loading: true, error: '' });
            var state = _state[0];
            var setState = _state[1];

            useEffect(function () {
                setState({ posts: [], loading: true, error: '' });
                var path = '/blockenberg/v1/post-grid?type=' + a.postType
                    + '&orderby=' + a.orderby
                    + '&order=' + a.order
                    + '&per_page=' + a.perPage
                    + '&offset=' + a.offset
                    + '&excerpt_len=' + a.excerptLen;

                wp.apiFetch({ path: path })
                    .then(function (res) {
                        setState({ posts: res.posts || [], loading: false, error: '' });
                    })
                    .catch(function (err) {
                        setState({ posts: [], loading: false, error: err.message || 'Error loading posts.' });
                    });
            }, [a.postType, a.orderby, a.order, a.perPage, a.offset, a.excerptLen]);

            var blockProps = useBlockProps({
                className: 'bkbg-pg-wrap bkbg-pg-card--' + a.cardStyle,
                style: (function () {
                    var _tvFn = getTypoCssVars();
                    var s = {
                        '--bkbg-pg-cols':        a.columns,
                        '--bkbg-pg-cols-tab':    a.columnsTablet,
                        '--bkbg-pg-cols-mob':    a.columnsMobile,
                        '--bkbg-pg-gap-x':       a.gapX + 'px',
                        '--bkbg-pg-gap-y':       a.gapY + 'px',
                        '--bkbg-pg-ratio':       a.imageRatio + '%',
                        '--bkbg-pg-card-bg':     a.cardBg,
                        '--bkbg-pg-card-radius': a.cardRadius + 'px',
                        '--bkbg-pg-title-color': a.titleColor,
                        '--bkbg-pg-title-hover': a.titleHoverColor,
                        '--bkbg-pg-exc-color':   a.excerptColor,
                        '--bkbg-pg-meta-color':  a.metaColor,
                        '--bkbg-pg-link-color':  a.linkColor,
                        '--bkbg-pg-border':      a.borderColor
                    };
                    Object.assign(s, _tvFn(a.titleTypo, '--bkbg-pgr-tt-'));
                    Object.assign(s, _tvFn(a.excerptTypo, '--bkbg-pgr-ex-'));
                    Object.assign(s, _tvFn(a.metaTypo, '--bkbg-pgr-mt-'));
                    return s;
                })()
            });

            var inspector = el(InspectorControls, null,

                /* ── Query ── */
                el(PanelBody, { title: __('Query', 'blockenberg'), initialOpen: true },
                    el(SelectControl, {
                        label: __('Post Type', 'blockenberg'),
                        value: a.postType,
                        options: [
                            { label: 'Posts', value: 'post' },
                            { label: 'Pages', value: 'page' },
                            { label: 'Products (WooCommerce)', value: 'product' }
                        ],
                        onChange: function (v) { setAttr({ postType: v }); }
                    }),
                    el(SelectControl, {
                        label: __('Order By', 'blockenberg'),
                        value: a.orderby,
                        options: orderbyOptions,
                        onChange: function (v) { setAttr({ orderby: v }); }
                    }),
                    el(SelectControl, {
                        label: __('Order', 'blockenberg'),
                        value: a.order,
                        options: [
                            { label: 'Newest first (DESC)', value: 'desc' },
                            { label: 'Oldest first (ASC)',  value: 'asc' }
                        ],
                        onChange: function (v) { setAttr({ order: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Posts per page', 'blockenberg'),
                        value: a.perPage, min: 1, max: 48,
                        onChange: function (v) { setAttr({ perPage: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Offset', 'blockenberg'),
                        value: a.offset, min: 0, max: 48,
                        onChange: function (v) { setAttr({ offset: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Excerpt word count', 'blockenberg'),
                        value: a.excerptLen, min: 5, max: 60,
                        onChange: function (v) { setAttr({ excerptLen: 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) { setAttr({ columns: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Columns (Tablet)', 'blockenberg'),
                        value: a.columnsTablet, min: 1, max: 4,
                        onChange: function (v) { setAttr({ columnsTablet: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Columns (Mobile)', 'blockenberg'),
                        value: a.columnsMobile, min: 1, max: 2,
                        onChange: function (v) { setAttr({ columnsMobile: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Column gap (px)', 'blockenberg'),
                        value: a.gapX, min: 0, max: 80,
                        onChange: function (v) { setAttr({ gapX: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Row gap (px)', 'blockenberg'),
                        value: a.gapY, min: 0, max: 80,
                        onChange: function (v) { setAttr({ gapY: v }); }
                    }),
                    el(SelectControl, {
                        label: __('Card Style', 'blockenberg'),
                        value: a.cardStyle,
                        options: cardStyleOptions,
                        onChange: function (v) { setAttr({ cardStyle: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Card border radius (px)', 'blockenberg'),
                        value: a.cardRadius, min: 0, max: 40,
                        onChange: function (v) { setAttr({ cardRadius: v }); }
                    })
                ),

                /* ── Image ── */
                el(PanelBody, { title: __('Image', 'blockenberg'), initialOpen: false },
                    el(ToggleControl, {
                        label: __('Show featured image', 'blockenberg'),
                        checked: a.showImage,
                        onChange: function (v) { setAttr({ showImage: v }); },
                        __nextHasNoMarginBottom: true
                    }),
                    a.showImage && el(Fragment, null,
                        el(SelectControl, {
                            label: __('Image ratio', 'blockenberg'),
                            value: a.imageRatio,
                            options: imageRatioOptions,
                            onChange: function (v) { setAttr({ imageRatio: v }); }
                        }),
                        el(SelectControl, {
                            label: __('Hover effect', 'blockenberg'),
                            value: a.imageHoverEffect,
                            options: hoverEffectOptions,
                            onChange: function (v) { setAttr({ imageHoverEffect: v }); }
                        })
                    )
                ),

                /* ── Content visibility ── */
                el(PanelBody, { title: __('Content', 'blockenberg'), initialOpen: false },
                    el(ToggleControl, {
                        label: __('Show meta (date / author)', 'blockenberg'),
                        checked: a.showMeta,
                        onChange: function (v) { setAttr({ showMeta: v }); },
                        __nextHasNoMarginBottom: true
                    }),
                    el(ToggleControl, {
                        label: __('Show excerpt', 'blockenberg'),
                        checked: a.showExcerpt,
                        onChange: function (v) { setAttr({ showExcerpt: v }); },
                        __nextHasNoMarginBottom: true
                    }),
                    el(ToggleControl, {
                        label: __('Show Read More', 'blockenberg'),
                        checked: a.showReadMore,
                        onChange: function (v) { setAttr({ showReadMore: v }); },
                        __nextHasNoMarginBottom: true
                    }),
                    a.showReadMore && el(Fragment, null,
                        el(TextControl, {
                            label: __('Read More label', 'blockenberg'),
                            value: a.readMoreLabel,
                            onChange: function (v) { setAttr({ readMoreLabel: v }); }
                        }),
                        el(SelectControl, {
                            label: __('Read More style', 'blockenberg'),
                            value: a.readMoreStyle,
                            options: readMoreStyleOptions,
                            onChange: function (v) { setAttr({ readMoreStyle: v }); }
                        })
                    ),
                    el(TextControl, {
                        label: __('No posts message', 'blockenberg'),
                        value: a.noPostsMessage,
                        onChange: function (v) { setAttr({ noPostsMessage: v }); }
                    })
                ),

                /* ── Typography ── */
                el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
                    (function () {
                        var TC = getTypoControl();
                        if (!TC) return el('p', null, 'Loading…');
                        return el(Fragment, null,
                            el(TC, { label: 'Title Typography', value: a.titleTypo, onChange: function (v) { setAttr({ titleTypo: v }); } }),
                            el(TC, { label: 'Excerpt Typography', value: a.excerptTypo, onChange: function (v) { setAttr({ excerptTypo: v }); } }),
                            el(TC, { label: 'Meta Typography', value: a.metaTypo, onChange: function (v) { setAttr({ metaTypo: v }); } })
                        );
                    })()
                ),

                /* ── Colors ── */
                el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
                    el(PanelColorSettings, {
                        title: __('Card Colors', 'blockenberg'),
                        initialOpen: false,
                        colorSettings: [
                            { label: __('Card background', 'blockenberg'), value: a.cardBg,          onChange: function(v){ setAttr({ cardBg: v||'#ffffff' }); } },
                            { label: __('Title color', 'blockenberg'),     value: a.titleColor,       onChange: function(v){ setAttr({ titleColor: v||'#111827' }); } },
                            { label: __('Title hover color', 'blockenberg'),value: a.titleHoverColor, onChange: function(v){ setAttr({ titleHoverColor: v||'#2563eb' }); } },
                            { label: __('Excerpt color', 'blockenberg'),   value: a.excerptColor,     onChange: function(v){ setAttr({ excerptColor: v||'#6b7280' }); } },
                            { label: __('Meta color', 'blockenberg'),      value: a.metaColor,        onChange: function(v){ setAttr({ metaColor: v||'#9ca3af' }); } },
                            { label: __('Link/button color', 'blockenberg'),value: a.linkColor,       onChange: function(v){ setAttr({ linkColor: v||'#2563eb' }); } },
                            { label: __('Border color', 'blockenberg'),    value: a.borderColor,      onChange: function(v){ setAttr({ borderColor: v||'#e5e7eb' }); } }
                        ]
                    })
                )
            );

            /* ─── Editor preview ─── */
            var content;
            if (state.loading) {
                content = el('div', { className: 'bkbg-pg-loading' },
                    el(Spinner),
                    el('span', null, ' ' + __('Loading posts…', 'blockenberg'))
                );
            } else if (state.error) {
                content = el(Notice, { status: 'error', isDismissible: false }, state.error);
            } else if (!state.posts.length) {
                content = el('p', { className: 'bkbg-pg-empty' }, a.noPostsMessage);
            } else {
                content = el('div', { className: 'bkbg-pg-grid' },
                    state.posts.map(function (post) { return renderCard(post, a); })
                );
            }

            return el(Fragment, null,
                inspector,
                el('div', blockProps, content)
            );
        },

        save: function (props) {
            var a = props.attributes;
            var _tvFn = getTypoCssVars();
            var saveStyle = {
                '--bkbg-pg-cols':        a.columns,
                '--bkbg-pg-cols-tab':    a.columnsTablet,
                '--bkbg-pg-cols-mob':    a.columnsMobile,
                '--bkbg-pg-gap-x':       a.gapX + 'px',
                '--bkbg-pg-gap-y':       a.gapY + 'px',
                '--bkbg-pg-ratio':       a.imageRatio + '%',
                '--bkbg-pg-card-bg':     a.cardBg,
                '--bkbg-pg-card-radius': a.cardRadius + 'px',
                '--bkbg-pg-title-color': a.titleColor,
                '--bkbg-pg-title-hover': a.titleHoverColor,
                '--bkbg-pg-exc-color':   a.excerptColor,
                '--bkbg-pg-meta-color':  a.metaColor,
                '--bkbg-pg-link-color':  a.linkColor,
                '--bkbg-pg-border':      a.borderColor
            };
            Object.assign(saveStyle, _tvFn(a.titleTypo, '--bkbg-pgr-tt-'));
            Object.assign(saveStyle, _tvFn(a.excerptTypo, '--bkbg-pgr-ex-'));
            Object.assign(saveStyle, _tvFn(a.metaTypo, '--bkbg-pgr-mt-'));

            var blockProps = wp.blockEditor.useBlockProps.save({
                className: 'bkbg-pg-wrap bkbg-pg-card--' + a.cardStyle,
                'data-post-type':   a.postType,
                'data-orderby':     a.orderby,
                'data-order':       a.order,
                'data-per-page':    a.perPage,
                'data-offset':      a.offset,
                'data-excerpt-len': a.excerptLen,
                'data-show-image':  a.showImage ? '1' : '0',
                'data-show-meta':   a.showMeta  ? '1' : '0',
                'data-show-excerpt':a.showExcerpt ? '1' : '0',
                'data-show-rm':     a.showReadMore ? '1' : '0',
                'data-rm-label':    a.readMoreLabel,
                'data-rm-style':    a.readMoreStyle,
                'data-no-posts':    a.noPostsMessage,
                'data-hover':       a.imageHoverEffect,
                style: saveStyle
            });
            return el('div', blockProps,
                el('div', { className: 'bkbg-pg-grid' })
            );
        }
    });
}() );

```
