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

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

638 lines 29.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 wp.domReady(function () {
2 var el = wp.element.createElement;
3 var Fragment = wp.element.Fragment;
4 var __ = wp.i18n.__;
5 var useState = wp.element.useState;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var BlockControls = wp.blockEditor.BlockControls;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
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 Button = wp.components.Button;
17 var ColorPicker = wp.components.ColorPicker;
18 var Popover = wp.components.Popover;
19 var ToolbarGroup = wp.components.ToolbarGroup;
20 var ToolbarButton = wp.components.ToolbarButton;
21
22 registerBlockType('blockenberg/progress-bar', {
23 title: __('Progress Bar', 'blockenberg'),
24 icon: 'chart-bar',
25 category: 'blockenberg',
26 description: __('Display animated progress bars with customizable styles and labels.', 'blockenberg'),
27
28 edit: function (props) {
29 var attributes = props.attributes;
30 var setAttributes = props.setAttributes;
31 var isSelected = props.isSelected;
32 var a = attributes;
33
34 // State for editing
35 var editingState = useState(null);
36 var editing = editingState[0];
37 var setEditing = editingState[1];
38
39 // State for hovered item
40 var hoveredState = useState(null);
41 var hovered = hoveredState[0];
42 var setHovered = hoveredState[1];
43
44 // State for color picker
45 var colorPickerState = useState(null);
46 var colorPicker = colorPickerState[0];
47 var setColorPicker = colorPickerState[1];
48
49 var layoutOptions = [
50 { label: __('Default', 'blockenberg'), value: 'default' },
51 { label: __('Rounded', 'blockenberg'), value: 'rounded' },
52 { label: __('Flat', 'blockenberg'), value: 'flat' },
53 { label: __('Thin', 'blockenberg'), value: 'thin' },
54 { label: __('Thick', 'blockenberg'), value: 'thick' }
55 ];
56
57 var percentagePositionOptions = [
58 { label: __('Right of Label', 'blockenberg'), value: 'right' },
59 { label: __('Inside Bar', 'blockenberg'), value: 'inside' },
60 { label: __('Above Bar', 'blockenberg'), value: 'above' },
61 { label: __('Hidden', 'blockenberg'), value: 'hidden' }
62 ];
63
64 var fontWeightOptions = [
65 { label: '400', value: 400 },
66 { label: '500', value: 500 },
67 { label: '600', value: 600 },
68 { label: '700', value: 700 }
69 ];
70
71 // Update item
72 function updateItem(index, key, value) {
73 var newItems = a.items.slice();
74 newItems[index] = Object.assign({}, newItems[index]);
75 newItems[index][key] = value;
76 setAttributes({ items: newItems });
77 }
78
79 // Add item
80 function addItem() {
81 var colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899'];
82 var newColor = colors[a.items.length % colors.length];
83 var newItems = a.items.concat([{
84 label: __('New Skill', 'blockenberg'),
85 percentage: 50,
86 color: newColor
87 }]);
88 setAttributes({ items: newItems });
89 }
90
91 // Remove item
92 function removeItem(index) {
93 var newItems = a.items.filter(function (_, i) { return i !== index; });
94 setAttributes({ items: newItems });
95 }
96
97 // Move item
98 function moveItem(index, direction) {
99 var newIndex = index + direction;
100 if (newIndex < 0 || newIndex >= a.items.length) return;
101 var newItems = a.items.slice();
102 var temp = newItems[index];
103 newItems[index] = newItems[newIndex];
104 newItems[newIndex] = temp;
105 setAttributes({ items: newItems });
106 }
107
108 // Inspector controls
109 var inspector = el(InspectorControls, {},
110 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
111 el(SelectControl, {
112 label: __('Style', 'blockenberg'),
113 value: a.layoutStyle,
114 options: layoutOptions,
115 onChange: function (v) { setAttributes({ layoutStyle: v }); }
116 }),
117 el(RangeControl, {
118 label: __('Bar Height', 'blockenberg'),
119 value: a.barHeight,
120 onChange: function (v) { setAttributes({ barHeight: v }); },
121 min: 4,
122 max: 60
123 }),
124 el(RangeControl, {
125 label: __('Bar Spacing', 'blockenberg'),
126 value: a.barSpacing,
127 onChange: function (v) { setAttributes({ barSpacing: v }); },
128 min: 8,
129 max: 60
130 }),
131 el(RangeControl, {
132 label: __('Border Radius', 'blockenberg'),
133 value: a.barRadius,
134 onChange: function (v) { setAttributes({ barRadius: v }); },
135 min: 0,
136 max: 30
137 })
138 ),
139
140 el(PanelBody, { title: __('Labels & Percentage', 'blockenberg'), initialOpen: false },
141 el(ToggleControl, {
142 label: __('Show Labels', 'blockenberg'),
143 checked: a.showLabels,
144 __nextHasNoMarginBottom: true,
145 onChange: function (v) { setAttributes({ showLabels: v }); }
146 }),
147 el(ToggleControl, {
148 label: __('Show Percentage', 'blockenberg'),
149 checked: a.showPercentage,
150 __nextHasNoMarginBottom: true,
151 onChange: function (v) { setAttributes({ showPercentage: v }); }
152 }),
153 a.showPercentage && el(SelectControl, {
154 label: __('Percentage Position', 'blockenberg'),
155 value: a.percentagePosition,
156 options: percentagePositionOptions,
157 onChange: function (v) { setAttributes({ percentagePosition: v }); }
158 }),
159 a.showLabels && el(RangeControl, {
160 label: __('Label Size', 'blockenberg'),
161 value: a.labelSize,
162 onChange: function (v) { setAttributes({ labelSize: v }); },
163 min: 12,
164 max: 24
165 }),
166 a.showLabels && el(SelectControl, {
167 label: __('Label Weight', 'blockenberg'),
168 value: a.labelWeight,
169 options: fontWeightOptions,
170 onChange: function (v) { setAttributes({ labelWeight: v }); }
171 }),
172 a.showPercentage && el(RangeControl, {
173 label: __('Percentage Size', 'blockenberg'),
174 value: a.percentageSize,
175 onChange: function (v) { setAttributes({ percentageSize: v }); },
176 min: 10,
177 max: 20
178 })
179 ),
180
181 el(PanelBody, { title: __('Effects', 'blockenberg'), initialOpen: false },
182 el(ToggleControl, {
183 label: __('Animate on Scroll', 'blockenberg'),
184 checked: a.animateOnScroll,
185 __nextHasNoMarginBottom: true,
186 onChange: function (v) { setAttributes({ animateOnScroll: v }); }
187 }),
188 a.animateOnScroll && el(RangeControl, {
189 label: __('Animation Duration (ms)', 'blockenberg'),
190 value: a.animationDuration,
191 onChange: function (v) { setAttributes({ animationDuration: v }); },
192 min: 200,
193 max: 3000,
194 step: 100
195 }),
196 el(ToggleControl, {
197 label: __('Stripes', 'blockenberg'),
198 checked: a.stripes,
199 __nextHasNoMarginBottom: true,
200 onChange: function (v) { setAttributes({ stripes: v }); }
201 }),
202 a.stripes && el(ToggleControl, {
203 label: __('Animated Stripes', 'blockenberg'),
204 checked: a.stripesAnimated,
205 __nextHasNoMarginBottom: true,
206 onChange: function (v) { setAttributes({ stripesAnimated: v }); }
207 }),
208 el(ToggleControl, {
209 label: __('Glow Effect', 'blockenberg'),
210 checked: a.glowEffect,
211 __nextHasNoMarginBottom: true,
212 onChange: function (v) { setAttributes({ glowEffect: v }); }
213 }),
214 el(ToggleControl, {
215 label: __('Gradient', 'blockenberg'),
216 checked: a.gradient,
217 __nextHasNoMarginBottom: true,
218 onChange: function (v) { setAttributes({ gradient: v }); }
219 }),
220 a.gradient && el(RangeControl, {
221 label: __('Gradient Angle', 'blockenberg'),
222 value: a.gradientAngle,
223 onChange: function (v) { setAttributes({ gradientAngle: v }); },
224 min: 0,
225 max: 360
226 })
227 ),
228
229 el(PanelBody, { title: __('Title', 'blockenberg'), initialOpen: false },
230 el(ToggleControl, {
231 label: __('Show Title', 'blockenberg'),
232 checked: a.showTitle,
233 __nextHasNoMarginBottom: true,
234 onChange: function (v) { setAttributes({ showTitle: v }); }
235 }),
236 a.showTitle && el(RangeControl, {
237 label: __('Title Size', 'blockenberg'),
238 value: a.titleSize,
239 onChange: function (v) { setAttributes({ titleSize: v }); },
240 min: 16,
241 max: 48
242 })
243 ),
244
245 el(PanelBody, { title: __('Progress Items', 'blockenberg'), initialOpen: false },
246 a.items.map(function (item, index) {
247 return el('div', { key: index, className: 'bkbg-pb-item-control' },
248 el('div', { className: 'bkbg-pb-item-header' },
249 el('span', { className: 'bkbg-pb-item-title' }, item.label || __('Item', 'blockenberg') + ' ' + (index + 1)),
250 el('div', { className: 'bkbg-pb-item-actions' },
251 el(Button, {
252 icon: 'arrow-up-alt2',
253 size: 'small',
254 disabled: index === 0,
255 onClick: function () { moveItem(index, -1); }
256 }),
257 el(Button, {
258 icon: 'arrow-down-alt2',
259 size: 'small',
260 disabled: index === a.items.length - 1,
261 onClick: function () { moveItem(index, 1); }
262 }),
263 el(Button, {
264 icon: 'trash',
265 size: 'small',
266 isDestructive: true,
267 onClick: function () { removeItem(index); }
268 })
269 )
270 ),
271 el(TextControl, {
272 label: __('Label', 'blockenberg'),
273 value: item.label,
274 onChange: function (v) { updateItem(index, 'label', v); }
275 }),
276 el(RangeControl, {
277 label: __('Percentage', 'blockenberg'),
278 value: item.percentage,
279 onChange: function (v) { updateItem(index, 'percentage', v); },
280 min: 0,
281 max: 100
282 }),
283 el('div', { className: 'bkbg-pb-color-row' },
284 el('span', {}, __('Color', 'blockenberg')),
285 el('button', {
286 className: 'bkbg-pb-color-btn',
287 style: { backgroundColor: item.color },
288 onClick: function () {
289 setColorPicker(colorPicker === index ? null : index);
290 }
291 }),
292 colorPicker === index && el(Popover, {
293 position: 'bottom left',
294 onClose: function () { setColorPicker(null); }
295 },
296 el(ColorPicker, {
297 color: item.color,
298 onChangeComplete: function (c) {
299 updateItem(index, 'color', c.hex);
300 }
301 })
302 )
303 )
304 );
305 }),
306 el(Button, {
307 variant: 'secondary',
308 onClick: addItem,
309 className: 'bkbg-pb-add-btn'
310 }, __('+ Add Progress Bar', 'blockenberg'))
311 ),
312
313 el(PanelColorSettings, {
314 title: __('Colors', 'blockenberg'),
315 initialOpen: false,
316 colorSettings: [
317 {
318 value: a.trackColor,
319 onChange: function (v) { setAttributes({ trackColor: v }); },
320 label: __('Track Color', 'blockenberg')
321 },
322 {
323 value: a.labelColor,
324 onChange: function (v) { setAttributes({ labelColor: v }); },
325 label: __('Label Color', 'blockenberg')
326 },
327 {
328 value: a.percentageColor,
329 onChange: function (v) { setAttributes({ percentageColor: v }); },
330 label: __('Percentage Color', 'blockenberg')
331 },
332 {
333 value: a.titleColor,
334 onChange: function (v) { setAttributes({ titleColor: v }); },
335 label: __('Title Color', 'blockenberg')
336 }
337 ]
338 })
339 );
340
341 // CSS variables
342 var wrapStyle = {
343 '--bkbg-pb-bar-height': a.barHeight + 'px',
344 '--bkbg-pb-bar-spacing': a.barSpacing + 'px',
345 '--bkbg-pb-bar-radius': a.barRadius + 'px',
346 '--bkbg-pb-track-color': a.trackColor,
347 '--bkbg-pb-label-color': a.labelColor,
348 '--bkbg-pb-label-size': a.labelSize + 'px',
349 '--bkbg-pb-label-weight': a.labelWeight,
350 '--bkbg-pb-percentage-color': a.percentageColor,
351 '--bkbg-pb-percentage-size': a.percentageSize + 'px',
352 '--bkbg-pb-title-color': a.titleColor,
353 '--bkbg-pb-title-size': a.titleSize + 'px'
354 };
355
356 // Title element
357 var titleEl = null;
358 if (a.showTitle) {
359 if (editing === 'title') {
360 titleEl = el('input', {
361 type: 'text',
362 className: 'bkbg-pb-title bkbg-pb-input-active',
363 value: a.title,
364 autoFocus: true,
365 placeholder: __('Progress Title', 'blockenberg'),
366 onChange: function (e) { setAttributes({ title: e.target.value }); },
367 onBlur: function () { setEditing(null); },
368 onKeyDown: function (e) { if (e.key === 'Enter') setEditing(null); }
369 });
370 } else {
371 titleEl = el('div', {
372 className: 'bkbg-pb-title bkbg-pb-clickable',
373 onClick: function () { setEditing('title'); }
374 }, a.title || __('Progress Title', 'blockenberg'));
375 }
376 }
377
378 // Lighten color helper
379 function lightenColor(hex, percent) {
380 var num = parseInt(hex.replace('#', ''), 16);
381 var r = Math.min(255, (num >> 16) + Math.round(255 * percent / 100));
382 var g = Math.min(255, ((num >> 8) & 0x00FF) + Math.round(255 * percent / 100));
383 var b = Math.min(255, (num & 0x0000FF) + Math.round(255 * percent / 100));
384 return '#' + (0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1);
385 }
386
387 // Get bar style
388 function getBarStyle(item) {
389 var style = {
390 width: item.percentage + '%'
391 };
392
393 // Background
394 if (a.gradient) {
395 var lighterColor = lightenColor(item.color, 30);
396 style.background = 'linear-gradient(' + a.gradientAngle + 'deg, ' + item.color + ' 0%, ' + lighterColor + ' 50%, ' + item.color + ' 100%)';
397 style.backgroundSize = '200% 100%';
398 } else {
399 style.backgroundColor = item.color;
400 }
401
402 // Stripes overlay
403 if (a.stripes) {
404 var stripesBg = 'linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)';
405 if (a.gradient) {
406 var lighterColor = lightenColor(item.color, 30);
407 style.background = stripesBg + ', linear-gradient(' + a.gradientAngle + 'deg, ' + item.color + ' 0%, ' + lighterColor + ' 50%, ' + item.color + ' 100%)';
408 } else {
409 style.background = stripesBg + ', ' + item.color;
410 }
411 style.backgroundSize = '40px 40px, 100% 100%';
412 }
413
414 // Glow effect
415 if (a.glowEffect) {
416 style.boxShadow = '0 0 10px ' + item.color + ', 0 0 20px ' + item.color + '60';
417 }
418
419 return style;
420 }
421
422 // Build progress bars
423 var progressBars = a.items.map(function (item, index) {
424 var labelKey = 'label-' + index;
425
426 // Percentage element based on position
427 var percentageEl = null;
428 if (a.showPercentage && a.percentagePosition !== 'hidden') {
429 percentageEl = el('span', { className: 'bkbg-pb-percentage' }, item.percentage + '%');
430 }
431
432 // Label element - editable
433 var labelEl = null;
434 if (a.showLabels) {
435 if (editing === labelKey) {
436 labelEl = el('input', {
437 type: 'text',
438 className: 'bkbg-pb-label bkbg-pb-input-active',
439 value: item.label,
440 autoFocus: true,
441 onChange: function (e) { updateItem(index, 'label', e.target.value); },
442 onBlur: function () { setEditing(null); },
443 onKeyDown: function (e) { if (e.key === 'Enter') setEditing(null); }
444 });
445 } else {
446 labelEl = el('span', {
447 className: 'bkbg-pb-label bkbg-pb-clickable',
448 onClick: function () { setEditing(labelKey); }
449 }, item.label);
450 }
451 }
452
453 // Header with label and percentage (if position is right)
454 var headerEl = null;
455 if (a.showLabels || (a.showPercentage && a.percentagePosition === 'right')) {
456 headerEl = el('div', { className: 'bkbg-pb-header' },
457 labelEl,
458 a.percentagePosition === 'right' && percentageEl
459 );
460 }
461
462 // Above bar percentage
463 var aboveEl = null;
464 if (a.showPercentage && a.percentagePosition === 'above') {
465 aboveEl = el('div', { className: 'bkbg-pb-above' },
466 el('span', {
467 className: 'bkbg-pb-percentage-above',
468 style: { left: item.percentage + '%' }
469 }, item.percentage + '%')
470 );
471 }
472
473 // Bar classes
474 var barClasses = 'bkbg-pb-bar';
475 if (a.stripes) barClasses += ' bkbg-pb-striped';
476 if (a.stripesAnimated) barClasses += ' bkbg-pb-striped-animated';
477
478 // Item actions (visible on hover)
479 var itemActions = el('div', { className: 'bkbg-pb-item-actions' },
480 el(Button, {
481 icon: 'arrow-up-alt2',
482 size: 'small',
483 disabled: index === 0,
484 onClick: function (e) { e.stopPropagation(); moveItem(index, -1); },
485 label: __('Move Up', 'blockenberg')
486 }),
487 el(Button, {
488 icon: 'arrow-down-alt2',
489 size: 'small',
490 disabled: index === a.items.length - 1,
491 onClick: function (e) { e.stopPropagation(); moveItem(index, 1); },
492 label: __('Move Down', 'blockenberg')
493 }),
494 el(Button, {
495 icon: 'admin-page',
496 size: 'small',
497 onClick: function (e) {
498 e.stopPropagation();
499 var newItems = a.items.slice();
500 newItems.splice(index + 1, 0, Object.assign({}, item));
501 setAttributes({ items: newItems });
502 },
503 label: __('Duplicate', 'blockenberg')
504 }),
505 el(Button, {
506 icon: 'trash',
507 size: 'small',
508 isDestructive: true,
509 onClick: function (e) { e.stopPropagation(); removeItem(index); },
510 label: __('Delete', 'blockenberg')
511 })
512 );
513
514 var itemClass = 'bkbg-pb-item';
515 if (hovered === index) itemClass += ' bkbg-pb-item-hovered';
516
517 return el('div', {
518 className: itemClass,
519 key: index,
520 onMouseEnter: function () { setHovered(index); },
521 onMouseLeave: function () { setHovered(null); }
522 },
523 itemActions,
524 headerEl,
525 aboveEl,
526 el('div', { className: 'bkbg-pb-track' },
527 el('div', {
528 className: barClasses,
529 style: getBarStyle(item)
530 },
531 a.percentagePosition === 'inside' && el('span', { className: 'bkbg-pb-percentage-inside' }, item.percentage + '%')
532 )
533 )
534 );
535 });
536
537 // Layout classes
538 var layoutClass = 'bkbg-pb-wrap bkbg-editor-wrap';
539 layoutClass += ' bkbg-pb-' + a.layoutStyle;
540 if (isSelected) layoutClass += ' bkbg-block-selected';
541
542 // Block props for proper selection handling
543 var blockProps = useBlockProps({
544 className: layoutClass,
545 style: wrapStyle,
546 'data-layout': a.layoutStyle,
547 'data-block-label': 'Progress Bar'
548 });
549
550 // Block toolbar
551 var blockToolbar = el(BlockControls, {},
552 el(ToolbarGroup, {},
553 el(ToolbarButton, {
554 icon: 'plus-alt2',
555 label: __('Add Progress Bar', 'blockenberg'),
556 onClick: addItem
557 })
558 )
559 );
560
561 // Add item action bar (shared editor UI)
562 var addButton = el('div', { className: 'bkbg-editor-actions' },
563 el(Button, {
564 variant: 'secondary',
565 icon: 'plus-alt2',
566 onClick: addItem
567 }, __('Add Bar', 'blockenberg'))
568 );
569
570 return el(Fragment, {},
571 blockToolbar,
572 inspector,
573 el('div', blockProps,
574 titleEl,
575 el('div', { className: 'bkbg-pb-list' }, progressBars),
576 addButton
577 )
578 );
579 },
580
581 save: function (props) {
582 var a = props.attributes;
583
584 // CSS variables
585 var wrapStyle = {
586 '--bkbg-pb-bar-height': a.barHeight + 'px',
587 '--bkbg-pb-bar-spacing': a.barSpacing + 'px',
588 '--bkbg-pb-bar-radius': a.barRadius + 'px',
589 '--bkbg-pb-track-color': a.trackColor,
590 '--bkbg-pb-label-color': a.labelColor,
591 '--bkbg-pb-label-size': a.labelSize + 'px',
592 '--bkbg-pb-label-weight': a.labelWeight,
593 '--bkbg-pb-percentage-color': a.percentageColor,
594 '--bkbg-pb-percentage-size': a.percentageSize + 'px',
595 '--bkbg-pb-title-color': a.titleColor,
596 '--bkbg-pb-title-size': a.titleSize + 'px',
597 '--bkbg-pb-animation-duration': a.animationDuration + 'ms'
598 };
599
600 // Serialize items for frontend
601 var itemsData = a.items.map(function (item) {
602 return item.label + '|' + item.percentage + '|' + item.color;
603 }).join(';;');
604
605 // Layout classes
606 var layoutClass = 'bkbg-pb-wrap';
607 layoutClass += ' bkbg-pb-' + a.layoutStyle;
608 if (a.stripes) layoutClass += ' bkbg-pb-striped';
609 if (a.stripesAnimated) layoutClass += ' bkbg-pb-striped-animated';
610 if (a.glowEffect) layoutClass += ' bkbg-pb-glow';
611 if (a.gradient) layoutClass += ' bkbg-pb-gradient';
612
613 // Title element
614 var titleEl = null;
615 if (a.showTitle && a.title) {
616 titleEl = el('div', { className: 'bkbg-pb-title' }, a.title);
617 }
618
619 return el('div', {
620 className: layoutClass,
621 style: wrapStyle,
622 'data-layout': a.layoutStyle,
623 'data-items': itemsData,
624 'data-animate': a.animateOnScroll ? '1' : '0',
625 'data-show-labels': a.showLabels ? '1' : '0',
626 'data-show-percentage': a.showPercentage ? '1' : '0',
627 'data-percentage-position': a.percentagePosition,
628 'data-gradient': a.gradient ? '1' : '0',
629 'data-gradient-angle': a.gradientAngle,
630 'data-glow': a.glowEffect ? '1' : '0'
631 },
632 titleEl,
633 el('div', { className: 'bkbg-pb-list' })
634 );
635 }
636 });
637 });
638