# blockenberg/2.0.7/blocks/progress-tracker/index.js

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

- Page: https://pluginprobe.com/plugins/blockenberg/2.0.7/code/blocks/progress-tracker/index.js
- Raw: https://pluginprobe.com/plugins/blockenberg/2.0.7/raw/blocks/progress-tracker/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/progress-tracker/index.js#L10-L20`.

```javascript
( function () {
    const el = window.wp.element.createElement;
    const { registerBlockType } = window.wp.blocks;
    const { InspectorControls, useBlockProps, PanelColorSettings } = window.wp.blockEditor;
    const { PanelBody, RangeControl, ToggleControl, TextControl, SelectControl, Button } = window.wp.components;
    const { __ } = window.wp.i18n;

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

    // ── Renderer ──────────────────────────────────────────────────────────────
    function renderTracker( a ) {
        const steps = a.steps || [];
        if ( ! steps.length ) return el( 'svg', { width: '100%', viewBox: `0 0 ${ a.svgWidth } 60` } );

        const W     = a.svgWidth;
        const R     = a.dotRadius;
        const LW    = a.lineWidth;
        const PAD   = R + 20;
        const usableW = W - PAD * 2;
        const labelFS = (a.labelTypo && a.labelTypo.sizeDesktop) || 13;
        const labelFW = (a.labelTypo && a.labelTypo.weight) || 600;
        const descFS  = (a.descTypo && a.descTypo.sizeDesktop) || 11;
        const descFW  = (a.descTypo && a.descTypo.weight) || 400;
        const dotY    = a.showDates ? R + 28 : R + 8;
        const descH   = a.showDescriptions ? descFS + 8 : 0;
        const labelH  = labelFS + 6;
        const totalH  = dotY + R + labelH + descH + ( a.showDates ? 0 : 0 ) + 20;

        const svgEls = [];
        svgEls.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: totalH, fill: a.bgColor, rx: 8 } ) );

        const stepX = steps.map( ( _, i ) => PAD + ( i / Math.max( steps.length - 1, 1 ) ) * usableW );

        // Connector lines between dots
        steps.forEach( ( step, i ) => {
            if ( i === steps.length - 1 ) return;
            const x1 = stepX[ i ] + R;
            const x2 = stepX[ i + 1 ] - R;

            // Completed segment = both endpoints done
            const bothDone = step.status === 'done' && steps[ i + 1 ].status === 'done';
            const leftDone = step.status === 'done' && steps[ i + 1 ].status === 'active';
            const midX = ( x1 + x2 ) / 2;

            if ( bothDone ) {
                svgEls.push( el( 'line', { key: `ln${ i }`, x1, y1: dotY, x2, y2: dotY, stroke: a.completedColor, strokeWidth: LW } ) );
            } else if ( leftDone ) {
                svgEls.push( el( 'line', { key: `lna${ i }`, x1, y1: dotY, x2: midX, y2: dotY, stroke: a.completedColor, strokeWidth: LW } ) );
                svgEls.push( el( 'line', { key: `lnb${ i }`, x1: midX, y1: dotY, x2, y2: dotY, stroke: a.pendingColor, strokeWidth: LW } ) );
            } else {
                svgEls.push( el( 'line', { key: `ln${ i }`, x1, y1: dotY, x2, y2: dotY, stroke: a.pendingColor, strokeWidth: LW } ) );
            }
        } );

        // Dots + labels
        steps.forEach( ( step, i ) => {
            const cx = stepX[ i ];
            const fillColor = step.status === 'done' ? a.completedColor : step.status === 'active' ? a.activeColor : a.pendingColor;
            const iconColor = step.status === 'pending' ? '#9ca3af' : '#ffffff';

            // Date above
            if ( a.showDates && step.date ) {
                svgEls.push( el( 'text', { key: `dt${ i }`, x: cx, y: dotY - R - 6, textAnchor: 'middle', fill: a.textColor, className: 'bkbg-trk-step-date', fillOpacity: 0.7 }, step.date ) );
            }

            // Shadow
            svgEls.push( el( 'circle', { key: `sh${ i }`, cx: cx + 2, cy: dotY + 2, r: R, fill: 'rgba(0,0,0,0.10)' } ) );
            // Dot
            svgEls.push( el( 'circle', { key: `dot${ i }`, cx, cy: dotY, r: R, fill: fillColor, stroke: '#fff', strokeWidth: 2 } ) );

            // Icon: checkmark for done, number for others
            if ( step.status === 'done' ) {
                const csz = R * 0.55;
                svgEls.push( el( 'polyline', { key: `ck${ i }`, points: `${ cx - csz * 0.55 } ${ dotY }, ${ cx - csz * 0.1 } ${ dotY + csz * 0.5 }, ${ cx + csz * 0.65 } ${ dotY - csz * 0.55 }`, stroke: '#ffffff', strokeWidth: R * 0.18, fill: 'none', strokeLinecap: 'round', strokeLinejoin: 'round' } ) );
            } else if ( step.status === 'active' ) {
                svgEls.push( el( 'circle', { key: `act${ i }`, cx, cy: dotY, r: R * 0.4, fill: '#ffffff', fillOpacity: 0.85 } ) );
            } else {
                svgEls.push( el( 'text', { key: `num${ i }`, x: cx, y: dotY, textAnchor: 'middle', dominantBaseline: 'middle', fill: iconColor, className: 'bkbg-trk-step-num' }, String( i + 1 ) ) );
            }

            // Step label
svgEls.push( el( 'text', { key: `lbl${ i }`, x: cx, y: dotY + R + labelFS, textAnchor: 'middle', fill: a.textColor, className: 'bkbg-trk-step-label' }, step.label ) );

            // Description
            if ( a.showDescriptions && step.description ) {
                svgEls.push( el( 'text', { key: `desc${ i }`, x: cx, y: dotY + R + labelFS + descFS + 4, textAnchor: 'middle', fill: a.textColor, className: 'bkbg-trk-step-desc', fillOpacity: 0.65 }, step.description ) );
            }
        } );

        return el( 'svg', { viewBox: `0 0 ${ W } ${ totalH }`, width: '100%', style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } }, ...svgEls );
    }

    function updStep( setAttributes, steps, si, field, val ) {
        setAttributes( { steps: steps.map( ( s, i ) => i === si ? { ...s, [field]: val } : s ) } );
    }

    registerBlockType( 'blockenberg/progress-tracker', {
        deprecated: [{
            attributes: {
                title: { type: 'string', default: 'Q1 2025 Objectives' },
                showTitle: { type: 'boolean', default: true },
                svgWidth: { type: 'number', default: 680 },
                dotRadius: { type: 'number', default: 18 },
                lineWidth: { type: 'number', default: 5 },
                showDates: { type: 'boolean', default: true },
                showDescriptions: { type: 'boolean', default: true },
                labelFontSize: { type: 'number', default: 13 },
                labelFontWeight: { type: 'number', default: 600 },
                labelLineHeight: { type: 'number', default: 1.4 },
                descFontSize: { type: 'number', default: 11 },
                descFontWeight: { type: 'number', default: 400 },
                descLineHeight: { type: 'number', default: 1.4 },
                completedColor: { type: 'string', default: '#10b981' },
                activeColor: { type: 'string', default: '#6366f1' },
                pendingColor: { type: 'string', default: '#d1d5db' },
                bgColor: { type: 'string', default: '#ffffff' },
                textColor: { type: 'string', default: '#374151' },
                titleColor: { type: 'string', default: '#111827' },
                steps: { type: 'array', default: [
                    { label: 'Planning', description: 'Define scope & roadmap', date: 'Jan 5', status: 'done' },
                    { label: 'Design', description: 'UX mockups & prototypes', date: 'Jan 20', status: 'done' },
                    { label: 'Development', description: 'Build core features', date: 'Feb 15', status: 'active' },
                    { label: 'QA Testing', description: 'Bug fixes & performance', date: 'Mar 5', status: 'pending' },
                    { label: 'Launch', description: 'Deploy to production', date: 'Mar 20', status: 'pending' }
                ]}
            },
            save: function ( { attributes: a } ) {
                const blockProps = useBlockProps.save( { className: 'bkbg-tracker-wrap' } );
                function renderTrackerV1( a ) {
                    const steps = a.steps || [];
                    if ( ! steps.length ) return el( 'svg', { width: '100%', viewBox: `0 0 ${ a.svgWidth } 60` } );
                    const W = a.svgWidth, R = a.dotRadius, LW = a.lineWidth, PAD = R + 20;
                    const usableW = W - PAD * 2;
                    const dotY = a.showDates ? R + 28 : R + 8;
                    const descH = a.showDescriptions ? a.descFontSize + 8 : 0;
                    const labelH = a.labelFontSize + 6;
                    const totalH = dotY + R + labelH + descH + 20;
                    const svgEls = [];
                    svgEls.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: totalH, fill: a.bgColor, rx: 8 } ) );
                    const stepX = steps.map( ( _, i ) => PAD + ( i / Math.max( steps.length - 1, 1 ) ) * usableW );
                    steps.forEach( ( step, i ) => {
                        if ( i === steps.length - 1 ) return;
                        const x1 = stepX[ i ] + R, x2 = stepX[ i + 1 ] - R;
                        const bothDone = step.status === 'done' && steps[ i + 1 ].status === 'done';
                        const leftDone = step.status === 'done' && steps[ i + 1 ].status === 'active';
                        const midX = ( x1 + x2 ) / 2;
                        if ( bothDone ) {
                            svgEls.push( el( 'line', { key: `ln${ i }`, x1, y1: dotY, x2, y2: dotY, stroke: a.completedColor, strokeWidth: LW } ) );
                        } else if ( leftDone ) {
                            svgEls.push( el( 'line', { key: `lna${ i }`, x1, y1: dotY, x2: midX, y2: dotY, stroke: a.completedColor, strokeWidth: LW } ) );
                            svgEls.push( el( 'line', { key: `lnb${ i }`, x1: midX, y1: dotY, x2, y2: dotY, stroke: a.pendingColor, strokeWidth: LW } ) );
                        } else {
                            svgEls.push( el( 'line', { key: `ln${ i }`, x1, y1: dotY, x2, y2: dotY, stroke: a.pendingColor, strokeWidth: LW } ) );
                        }
                    } );
                    steps.forEach( ( step, i ) => {
                        const cx = stepX[ i ];
                        const fillColor = step.status === 'done' ? a.completedColor : step.status === 'active' ? a.activeColor : a.pendingColor;
                        const iconColor = step.status === 'pending' ? '#9ca3af' : '#ffffff';
                        if ( a.showDates && step.date ) {
                            svgEls.push( el( 'text', { key: `dt${ i }`, x: cx, y: dotY - R - 6, textAnchor: 'middle', fill: a.textColor, fontSize: a.descFontSize, fontFamily: 'inherit', fillOpacity: 0.7 }, step.date ) );
                        }
                        svgEls.push( el( 'circle', { key: `sh${ i }`, cx: cx + 2, cy: dotY + 2, r: R, fill: 'rgba(0,0,0,0.10)' } ) );
                        svgEls.push( el( 'circle', { key: `dot${ i }`, cx, cy: dotY, r: R, fill: fillColor, stroke: '#fff', strokeWidth: 2 } ) );
                        if ( step.status === 'done' ) {
                            const csz = R * 0.55;
                            svgEls.push( el( 'polyline', { key: `ck${ i }`, points: `${ cx - csz * 0.55 } ${ dotY }, ${ cx - csz * 0.1 } ${ dotY + csz * 0.5 }, ${ cx + csz * 0.65 } ${ dotY - csz * 0.55 }`, stroke: '#ffffff', strokeWidth: R * 0.18, fill: 'none', strokeLinecap: 'round', strokeLinejoin: 'round' } ) );
                        } else if ( step.status === 'active' ) {
                            svgEls.push( el( 'circle', { key: `act${ i }`, cx, cy: dotY, r: R * 0.4, fill: '#ffffff', fillOpacity: 0.85 } ) );
                        } else {
                            svgEls.push( el( 'text', { key: `num${ i }`, x: cx, y: dotY, textAnchor: 'middle', dominantBaseline: 'middle', fill: iconColor, fontSize: a.descFontSize, fontFamily: 'inherit', fontWeight: a.descFontWeight }, String( i + 1 ) ) );
                        }
                        svgEls.push( el( 'text', { key: `lbl${ i }`, x: cx, y: dotY + R + a.labelFontSize, textAnchor: 'middle', fill: a.textColor, fontSize: a.labelFontSize, fontFamily: 'inherit', fontWeight: a.labelFontWeight }, step.label ) );
                        if ( a.showDescriptions && step.description ) {
                            svgEls.push( el( 'text', { key: `desc${ i }`, x: cx, y: dotY + R + a.labelFontSize + a.descFontSize + 4, textAnchor: 'middle', fill: a.textColor, fontSize: a.descFontSize, fontFamily: 'inherit', fillOpacity: 0.65 }, step.description ) );
                        }
                    } );
                    return el( 'svg', { viewBox: `0 0 ${ W } ${ totalH }`, width: '100%', style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } }, ...svgEls );
                }
                return el( 'div', blockProps,
                    a.showTitle && a.title ? el( 'h3', { className: 'bkbg-tracker-title', style: { color: a.titleColor } }, a.title ) : null,
                    el( 'div', { className: 'bkbg-tracker-svg' }, renderTrackerV1( a ) ),
                );
            }
        }],

        edit: function ( props ) {
            const { attributes: a, setAttributes } = props;
            const TC = getTypoControl();
            const blockProps = useBlockProps((function () {
                const _tv = getTypoCssVars();
                const s = {};
                if (_tv) {
                    Object.assign(s, _tv(a.titleTypo || {}, '--bkbg-trk-tt-'));
                    Object.assign(s, _tv(a.labelTypo || {}, '--bkbg-trk-lb-'));
                    Object.assign(s, _tv(a.descTypo || {}, '--bkbg-trk-ds-'));
                }
                return { className: 'bkbg-tracker-wrap', style: s };
            })());

            return el( 'div', blockProps,
                el( InspectorControls, {},

                    el( PanelBody, { title: __( 'Settings', 'blockenberg' ), initialOpen: true },
                        el( TextControl,   { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ),
                        el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ),        checked: a.showTitle,        onChange: v => setAttributes( { showTitle:        v } ) } ),
                        el( ToggleControl, { label: __( 'Show Dates', 'blockenberg' ),        checked: a.showDates,        onChange: v => setAttributes( { showDates:        v } ) } ),
                        el( ToggleControl, { label: __( 'Show Descriptions', 'blockenberg' ), checked: a.showDescriptions, onChange: v => setAttributes( { showDescriptions: v } ) } ),
                    ),

                    el( PanelBody, { title: __( 'Layout', 'blockenberg' ), initialOpen: false },
                        el( RangeControl, { label: __( 'Canvas Width', 'blockenberg' ),   value: a.svgWidth,     onChange: v => setAttributes( { svgWidth:     v } ), min: 300, max: 1200, step: 20 } ),
                        el( RangeControl, { label: __( 'Dot Radius', 'blockenberg' ),     value: a.dotRadius,    onChange: v => setAttributes( { dotRadius:    v } ), min: 10, max: 40 } ),
                        el( RangeControl, { label: __( 'Line Width', 'blockenberg' ),     value: a.lineWidth,    onChange: v => setAttributes( { lineWidth:    v } ), min: 2, max: 12 } ),
                    ),


                    el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
                        TC && el(TC, { label: __('Title', 'blockenberg'), value: a.titleTypo || {}, onChange: v => setAttributes({ titleTypo: v }) }),
                        TC && el(TC, { label: __('Step Label', 'blockenberg'), value: a.labelTypo || {}, onChange: v => setAttributes({ labelTypo: v }) }),
                        TC && el(TC, { label: __('Step Description', 'blockenberg'), value: a.descTypo || {}, onChange: v => setAttributes({ descTypo: v }) })
                    ),
el( PanelColorSettings, {
                        title: __( 'Colors', 'blockenberg' ), initialOpen: false,
                        colorSettings: [
                            { label: __( 'Background', 'blockenberg' ),      value: a.bgColor,        onChange: v => setAttributes( { bgColor:        v || '#ffffff' } ) },
                            { label: __( 'Completed',  'blockenberg' ),      value: a.completedColor, onChange: v => setAttributes( { completedColor: v || '#10b981' } ) },
                            { label: __( 'Active',     'blockenberg' ),      value: a.activeColor,    onChange: v => setAttributes( { activeColor:    v || '#6366f1' } ) },
                            { label: __( 'Pending',    'blockenberg' ),      value: a.pendingColor,   onChange: v => setAttributes( { pendingColor:   v || '#d1d5db' } ) },
                            { label: __( 'Text',       'blockenberg' ),      value: a.textColor,      onChange: v => setAttributes( { textColor:      v || '#374151' } ) },
                            { label: __( 'Title',      'blockenberg' ),      value: a.titleColor,     onChange: v => setAttributes( { titleColor:     v || '#111827' } ) },
                        ]
                    } ),

                    el( PanelBody, { title: __( 'Steps', 'blockenberg' ), initialOpen: false },
                        el( Button, { variant: 'secondary', style: { marginBottom: 10 }, onClick: () => setAttributes( { steps: [ ...( a.steps || [] ), { label: 'New Milestone', description: '', date: '', status: 'pending' } ] } ) }, __( '+ Add Step', 'blockenberg' ) ),
                        a.steps.map( ( step, si ) =>
                            el( PanelBody, { key: si, title: `${ si + 1 }. ${ step.label }`, initialOpen: false },
                                el( TextControl,   { label: __( 'Label', 'blockenberg' ),       value: step.label,       onChange: v => updStep( setAttributes, a.steps, si, 'label',       v ) } ),
                                el( TextControl,   { label: __( 'Date', 'blockenberg' ),        value: step.date,        onChange: v => updStep( setAttributes, a.steps, si, 'date',        v ) } ),
                                el( TextControl,   { label: __( 'Description', 'blockenberg' ), value: step.description, onChange: v => updStep( setAttributes, a.steps, si, 'description', v ) } ),
                                el( SelectControl, {
                                    label: __( 'Status', 'blockenberg' ),
                                    value: step.status,
                                    options: [ { label: __( 'Done', 'blockenberg' ), value: 'done' }, { label: __( 'Active', 'blockenberg' ), value: 'active' }, { label: __( 'Pending', 'blockenberg' ), value: 'pending' } ],
                                    onChange: v => updStep( setAttributes, a.steps, si, 'status', v ),
                                } ),
                                el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { steps: a.steps.filter( ( _, x ) => x !== si ) } ) }, __( 'Remove Step', 'blockenberg' ) ),
                            )
                        ),
                    ),
                ),

                a.showTitle && a.title && el( 'h3', { className: 'bkbg-tracker-title', style: { color: a.titleColor } }, a.title ),
                el( 'div', { className: 'bkbg-tracker-svg' }, renderTracker( a ) ),
            );
        },

        save: function ( { attributes: a } ) {
            const blockProps = useBlockProps.save((function () {
                const _tv = getTypoCssVars();
                const s = {};
                if (_tv) {
                    Object.assign(s, _tv(a.titleTypo || {}, '--bkbg-trk-tt-'));
                    Object.assign(s, _tv(a.labelTypo || {}, '--bkbg-trk-lb-'));
                    Object.assign(s, _tv(a.descTypo || {}, '--bkbg-trk-ds-'));
                }
                return { className: 'bkbg-tracker-wrap', style: s };
            })());
            return el( 'div', blockProps,
                a.showTitle && a.title ? el( 'h3', { className: 'bkbg-tracker-title', style: { color: a.titleColor } }, a.title ) : null,
                el( 'div', { className: 'bkbg-tracker-svg' }, renderTracker( a ) ),
            );
        },
    } );
}() );

```
