PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.11
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.11
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 / progress-tracker / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.11, at blocks/progress-tracker/index.js

278 lines 19.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 const el = window.wp.element.createElement;
3 const { registerBlockType } = window.wp.blocks;
4 const { InspectorControls, useBlockProps, PanelColorSettings } = window.wp.blockEditor;
5 const { PanelBody, RangeControl, ToggleControl, TextControl, SelectControl, Button } = window.wp.components;
6 const { __ } = window.wp.i18n;
7
8 let _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
9 let _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
10
11 // ── Renderer ──────────────────────────────────────────────────────────────
12 function renderTracker( a ) {
13 const steps = a.steps || [];
14 if ( ! steps.length ) return el( 'svg', { width: '100%', viewBox: `0 0 ${ a.svgWidth } 60` } );
15
16 const W = a.svgWidth;
17 const R = a.dotRadius;
18 const LW = a.lineWidth;
19 const PAD = R + 20;
20 const usableW = W - PAD * 2;
21 const labelFS = (a.labelTypo && a.labelTypo.sizeDesktop) || 13;
22 const labelFW = (a.labelTypo && a.labelTypo.weight) || 600;
23 const descFS = (a.descTypo && a.descTypo.sizeDesktop) || 11;
24 const descFW = (a.descTypo && a.descTypo.weight) || 400;
25 const dotY = a.showDates ? R + 28 : R + 8;
26 const descH = a.showDescriptions ? descFS + 8 : 0;
27 const labelH = labelFS + 6;
28 const totalH = dotY + R + labelH + descH + ( a.showDates ? 0 : 0 ) + 20;
29
30 const svgEls = [];
31 svgEls.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: totalH, fill: a.bgColor, rx: 8 } ) );
32
33 const stepX = steps.map( ( _, i ) => PAD + ( i / Math.max( steps.length - 1, 1 ) ) * usableW );
34
35 // Connector lines between dots
36 steps.forEach( ( step, i ) => {
37 if ( i === steps.length - 1 ) return;
38 const x1 = stepX[ i ] + R;
39 const x2 = stepX[ i + 1 ] - R;
40
41 // Completed segment = both endpoints done
42 const bothDone = step.status === 'done' && steps[ i + 1 ].status === 'done';
43 const leftDone = step.status === 'done' && steps[ i + 1 ].status === 'active';
44 const midX = ( x1 + x2 ) / 2;
45
46 if ( bothDone ) {
47 svgEls.push( el( 'line', { key: `ln${ i }`, x1, y1: dotY, x2, y2: dotY, stroke: a.completedColor, strokeWidth: LW } ) );
48 } else if ( leftDone ) {
49 svgEls.push( el( 'line', { key: `lna${ i }`, x1, y1: dotY, x2: midX, y2: dotY, stroke: a.completedColor, strokeWidth: LW } ) );
50 svgEls.push( el( 'line', { key: `lnb${ i }`, x1: midX, y1: dotY, x2, y2: dotY, stroke: a.pendingColor, strokeWidth: LW } ) );
51 } else {
52 svgEls.push( el( 'line', { key: `ln${ i }`, x1, y1: dotY, x2, y2: dotY, stroke: a.pendingColor, strokeWidth: LW } ) );
53 }
54 } );
55
56 // Dots + labels
57 steps.forEach( ( step, i ) => {
58 const cx = stepX[ i ];
59 const fillColor = step.status === 'done' ? a.completedColor : step.status === 'active' ? a.activeColor : a.pendingColor;
60 const iconColor = step.status === 'pending' ? '#9ca3af' : '#ffffff';
61
62 // Date above
63 if ( a.showDates && step.date ) {
64 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 ) );
65 }
66
67 // Shadow
68 svgEls.push( el( 'circle', { key: `sh${ i }`, cx: cx + 2, cy: dotY + 2, r: R, fill: 'rgba(0,0,0,0.10)' } ) );
69 // Dot
70 svgEls.push( el( 'circle', { key: `dot${ i }`, cx, cy: dotY, r: R, fill: fillColor, stroke: '#fff', strokeWidth: 2 } ) );
71
72 // Icon: checkmark for done, number for others
73 if ( step.status === 'done' ) {
74 const csz = R * 0.55;
75 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' } ) );
76 } else if ( step.status === 'active' ) {
77 svgEls.push( el( 'circle', { key: `act${ i }`, cx, cy: dotY, r: R * 0.4, fill: '#ffffff', fillOpacity: 0.85 } ) );
78 } else {
79 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 ) ) );
80 }
81
82 // Step label
83 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 ) );
84
85 // Description
86 if ( a.showDescriptions && step.description ) {
87 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 ) );
88 }
89 } );
90
91 return el( 'svg', { viewBox: `0 0 ${ W } ${ totalH }`, width: '100%', style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } }, ...svgEls );
92 }
93
94 function updStep( setAttributes, steps, si, field, val ) {
95 setAttributes( { steps: steps.map( ( s, i ) => i === si ? { ...s, [field]: val } : s ) } );
96 }
97
98 registerBlockType( 'blockenberg/progress-tracker', {
99 deprecated: [{
100 attributes: {
101 title: { type: 'string', default: 'Q1 2025 Objectives' },
102 showTitle: { type: 'boolean', default: true },
103 svgWidth: { type: 'number', default: 680 },
104 dotRadius: { type: 'number', default: 18 },
105 lineWidth: { type: 'number', default: 5 },
106 showDates: { type: 'boolean', default: true },
107 showDescriptions: { type: 'boolean', default: true },
108 labelFontSize: { type: 'number', default: 13 },
109 labelFontWeight: { type: 'number', default: 600 },
110 labelLineHeight: { type: 'number', default: 1.4 },
111 descFontSize: { type: 'number', default: 11 },
112 descFontWeight: { type: 'number', default: 400 },
113 descLineHeight: { type: 'number', default: 1.4 },
114 completedColor: { type: 'string', default: '#10b981' },
115 activeColor: { type: 'string', default: '#6366f1' },
116 pendingColor: { type: 'string', default: '#d1d5db' },
117 bgColor: { type: 'string', default: '#ffffff' },
118 textColor: { type: 'string', default: '#374151' },
119 titleColor: { type: 'string', default: '#111827' },
120 steps: { type: 'array', default: [
121 { label: 'Planning', description: 'Define scope & roadmap', date: 'Jan 5', status: 'done' },
122 { label: 'Design', description: 'UX mockups & prototypes', date: 'Jan 20', status: 'done' },
123 { label: 'Development', description: 'Build core features', date: 'Feb 15', status: 'active' },
124 { label: 'QA Testing', description: 'Bug fixes & performance', date: 'Mar 5', status: 'pending' },
125 { label: 'Launch', description: 'Deploy to production', date: 'Mar 20', status: 'pending' }
126 ]}
127 },
128 save: function ( { attributes: a } ) {
129 const blockProps = useBlockProps.save( { className: 'bkbg-tracker-wrap' } );
130 function renderTrackerV1( a ) {
131 const steps = a.steps || [];
132 if ( ! steps.length ) return el( 'svg', { width: '100%', viewBox: `0 0 ${ a.svgWidth } 60` } );
133 const W = a.svgWidth, R = a.dotRadius, LW = a.lineWidth, PAD = R + 20;
134 const usableW = W - PAD * 2;
135 const dotY = a.showDates ? R + 28 : R + 8;
136 const descH = a.showDescriptions ? a.descFontSize + 8 : 0;
137 const labelH = a.labelFontSize + 6;
138 const totalH = dotY + R + labelH + descH + 20;
139 const svgEls = [];
140 svgEls.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: totalH, fill: a.bgColor, rx: 8 } ) );
141 const stepX = steps.map( ( _, i ) => PAD + ( i / Math.max( steps.length - 1, 1 ) ) * usableW );
142 steps.forEach( ( step, i ) => {
143 if ( i === steps.length - 1 ) return;
144 const x1 = stepX[ i ] + R, x2 = stepX[ i + 1 ] - R;
145 const bothDone = step.status === 'done' && steps[ i + 1 ].status === 'done';
146 const leftDone = step.status === 'done' && steps[ i + 1 ].status === 'active';
147 const midX = ( x1 + x2 ) / 2;
148 if ( bothDone ) {
149 svgEls.push( el( 'line', { key: `ln${ i }`, x1, y1: dotY, x2, y2: dotY, stroke: a.completedColor, strokeWidth: LW } ) );
150 } else if ( leftDone ) {
151 svgEls.push( el( 'line', { key: `lna${ i }`, x1, y1: dotY, x2: midX, y2: dotY, stroke: a.completedColor, strokeWidth: LW } ) );
152 svgEls.push( el( 'line', { key: `lnb${ i }`, x1: midX, y1: dotY, x2, y2: dotY, stroke: a.pendingColor, strokeWidth: LW } ) );
153 } else {
154 svgEls.push( el( 'line', { key: `ln${ i }`, x1, y1: dotY, x2, y2: dotY, stroke: a.pendingColor, strokeWidth: LW } ) );
155 }
156 } );
157 steps.forEach( ( step, i ) => {
158 const cx = stepX[ i ];
159 const fillColor = step.status === 'done' ? a.completedColor : step.status === 'active' ? a.activeColor : a.pendingColor;
160 const iconColor = step.status === 'pending' ? '#9ca3af' : '#ffffff';
161 if ( a.showDates && step.date ) {
162 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 ) );
163 }
164 svgEls.push( el( 'circle', { key: `sh${ i }`, cx: cx + 2, cy: dotY + 2, r: R, fill: 'rgba(0,0,0,0.10)' } ) );
165 svgEls.push( el( 'circle', { key: `dot${ i }`, cx, cy: dotY, r: R, fill: fillColor, stroke: '#fff', strokeWidth: 2 } ) );
166 if ( step.status === 'done' ) {
167 const csz = R * 0.55;
168 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' } ) );
169 } else if ( step.status === 'active' ) {
170 svgEls.push( el( 'circle', { key: `act${ i }`, cx, cy: dotY, r: R * 0.4, fill: '#ffffff', fillOpacity: 0.85 } ) );
171 } else {
172 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 ) ) );
173 }
174 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 ) );
175 if ( a.showDescriptions && step.description ) {
176 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 ) );
177 }
178 } );
179 return el( 'svg', { viewBox: `0 0 ${ W } ${ totalH }`, width: '100%', style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } }, ...svgEls );
180 }
181 return el( 'div', blockProps,
182 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-tracker-title', style: { color: a.titleColor } }, a.title ) : null,
183 el( 'div', { className: 'bkbg-tracker-svg' }, renderTrackerV1( a ) ),
184 );
185 }
186 }],
187
188 edit: function ( props ) {
189 const { attributes: a, setAttributes } = props;
190 const TC = getTypoControl();
191 const blockProps = useBlockProps((function () {
192 const _tv = getTypoCssVars();
193 const s = {};
194 if (_tv) {
195 Object.assign(s, _tv(a.titleTypo || {}, '--bkbg-trk-tt-'));
196 Object.assign(s, _tv(a.labelTypo || {}, '--bkbg-trk-lb-'));
197 Object.assign(s, _tv(a.descTypo || {}, '--bkbg-trk-ds-'));
198 }
199 return { className: 'bkbg-tracker-wrap', style: s };
200 })());
201
202 return el( 'div', blockProps,
203 el( InspectorControls, {},
204
205 el( PanelBody, { title: __( 'Settings', 'blockenberg' ), initialOpen: true },
206 el( TextControl, { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ),
207 el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ), checked: a.showTitle, onChange: v => setAttributes( { showTitle: v } ) } ),
208 el( ToggleControl, { label: __( 'Show Dates', 'blockenberg' ), checked: a.showDates, onChange: v => setAttributes( { showDates: v } ) } ),
209 el( ToggleControl, { label: __( 'Show Descriptions', 'blockenberg' ), checked: a.showDescriptions, onChange: v => setAttributes( { showDescriptions: v } ) } ),
210 ),
211
212 el( PanelBody, { title: __( 'Layout', 'blockenberg' ), initialOpen: false },
213 el( RangeControl, { label: __( 'Canvas Width', 'blockenberg' ), value: a.svgWidth, onChange: v => setAttributes( { svgWidth: v } ), min: 300, max: 1200, step: 20 } ),
214 el( RangeControl, { label: __( 'Dot Radius', 'blockenberg' ), value: a.dotRadius, onChange: v => setAttributes( { dotRadius: v } ), min: 10, max: 40 } ),
215 el( RangeControl, { label: __( 'Line Width', 'blockenberg' ), value: a.lineWidth, onChange: v => setAttributes( { lineWidth: v } ), min: 2, max: 12 } ),
216 ),
217
218
219 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
220 TC && el(TC, { label: __('Title', 'blockenberg'), value: a.titleTypo || {}, onChange: v => setAttributes({ titleTypo: v }) }),
221 TC && el(TC, { label: __('Step Label', 'blockenberg'), value: a.labelTypo || {}, onChange: v => setAttributes({ labelTypo: v }) }),
222 TC && el(TC, { label: __('Step Description', 'blockenberg'), value: a.descTypo || {}, onChange: v => setAttributes({ descTypo: v }) })
223 ),
224 el( PanelColorSettings, {
225 title: __( 'Colors', 'blockenberg' ), initialOpen: false,
226 colorSettings: [
227 { label: __( 'Background', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#ffffff' } ) },
228 { label: __( 'Completed', 'blockenberg' ), value: a.completedColor, onChange: v => setAttributes( { completedColor: v || '#10b981' } ) },
229 { label: __( 'Active', 'blockenberg' ), value: a.activeColor, onChange: v => setAttributes( { activeColor: v || '#6366f1' } ) },
230 { label: __( 'Pending', 'blockenberg' ), value: a.pendingColor, onChange: v => setAttributes( { pendingColor: v || '#d1d5db' } ) },
231 { label: __( 'Text', 'blockenberg' ), value: a.textColor, onChange: v => setAttributes( { textColor: v || '#374151' } ) },
232 { label: __( 'Title', 'blockenberg' ), value: a.titleColor, onChange: v => setAttributes( { titleColor: v || '#111827' } ) },
233 ]
234 } ),
235
236 el( PanelBody, { title: __( 'Steps', 'blockenberg' ), initialOpen: false },
237 el( Button, { variant: 'secondary', style: { marginBottom: 10 }, onClick: () => setAttributes( { steps: [ ...( a.steps || [] ), { label: 'New Milestone', description: '', date: '', status: 'pending' } ] } ) }, __( '+ Add Step', 'blockenberg' ) ),
238 a.steps.map( ( step, si ) =>
239 el( PanelBody, { key: si, title: `${ si + 1 }. ${ step.label }`, initialOpen: false },
240 el( TextControl, { label: __( 'Label', 'blockenberg' ), value: step.label, onChange: v => updStep( setAttributes, a.steps, si, 'label', v ) } ),
241 el( TextControl, { label: __( 'Date', 'blockenberg' ), value: step.date, onChange: v => updStep( setAttributes, a.steps, si, 'date', v ) } ),
242 el( TextControl, { label: __( 'Description', 'blockenberg' ), value: step.description, onChange: v => updStep( setAttributes, a.steps, si, 'description', v ) } ),
243 el( SelectControl, {
244 label: __( 'Status', 'blockenberg' ),
245 value: step.status,
246 options: [ { label: __( 'Done', 'blockenberg' ), value: 'done' }, { label: __( 'Active', 'blockenberg' ), value: 'active' }, { label: __( 'Pending', 'blockenberg' ), value: 'pending' } ],
247 onChange: v => updStep( setAttributes, a.steps, si, 'status', v ),
248 } ),
249 el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { steps: a.steps.filter( ( _, x ) => x !== si ) } ) }, __( 'Remove Step', 'blockenberg' ) ),
250 )
251 ),
252 ),
253 ),
254
255 a.showTitle && a.title && el( 'h3', { className: 'bkbg-tracker-title', style: { color: a.titleColor } }, a.title ),
256 el( 'div', { className: 'bkbg-tracker-svg' }, renderTracker( a ) ),
257 );
258 },
259
260 save: function ( { attributes: a } ) {
261 const blockProps = useBlockProps.save((function () {
262 const _tv = getTypoCssVars();
263 const s = {};
264 if (_tv) {
265 Object.assign(s, _tv(a.titleTypo || {}, '--bkbg-trk-tt-'));
266 Object.assign(s, _tv(a.labelTypo || {}, '--bkbg-trk-lb-'));
267 Object.assign(s, _tv(a.descTypo || {}, '--bkbg-trk-ds-'));
268 }
269 return { className: 'bkbg-tracker-wrap', style: s };
270 })());
271 return el( 'div', blockProps,
272 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-tracker-title', style: { color: a.titleColor } }, a.title ) : null,
273 el( 'div', { className: 'bkbg-tracker-svg' }, renderTracker( a ) ),
274 );
275 },
276 } );
277 }() );
278