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

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/kpi-tiles/index.js

351 lines 18.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var __ = wp.i18n.__;
4 var registerBlockType = wp.blocks.registerBlockType;
5 var useBlockProps = wp.blockEditor.useBlockProps;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
8 var PanelBody = wp.components.PanelBody;
9 var RangeControl = wp.components.RangeControl;
10 var ToggleControl = wp.components.ToggleControl;
11 var SelectControl = wp.components.SelectControl;
12 var TextControl = wp.components.TextControl;
13 var Button = wp.components.Button;
14
15 var _tc, _tv;
16 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
17 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
18 var IP = function () { return window.bkbgIconPicker; };
19
20 var DIRECTIONS = ['up', 'down', 'down-good', 'neutral'];
21
22 function trendIcon(dir) {
23 if (dir === 'up') return '';
24 if (dir === 'down') return '';
25 if (dir === 'down-good') return '';
26 return '';
27 }
28
29 function trendClass(dir) {
30 if (dir === 'up') return 'bkbg-kpi-trend-up';
31 if (dir === 'down') return 'bkbg-kpi-trend-down';
32 if (dir === 'down-good') return 'bkbg-kpi-trend-downgood';
33 return 'bkbg-kpi-trend-neutral';
34 }
35
36 /* ── Sparkline SVG ─────────────────────────────────────────────── */
37 function SparklineEl(sparkStr, color, fill, width, height) {
38 var nums = (sparkStr || '').split(',').map(Number).filter(function (n) { return !isNaN(n); });
39 if (nums.length < 2) return null;
40 var minV = Math.min.apply(null, nums);
41 var maxV = Math.max.apply(null, nums);
42 var range = maxV - minV || 1;
43 var w = width; var h = height;
44 var step = w / (nums.length - 1);
45 var points = nums.map(function (n, i) {
46 return (i * step).toFixed(1) + ',' + ((1 - (n - minV) / range) * (h - 4) + 2).toFixed(1);
47 }).join(' ');
48 var fillPath = 'M ' + points.split(' ').join(' L ') +
49 ' L ' + w + ',' + h + ' L 0,' + h + ' Z';
50 return el('svg', {
51 viewBox: '0 0 ' + w + ' ' + h,
52 width: w, height: h,
53 xmlns: 'http://www.w3.org/2000/svg',
54 className: 'bkbg-kpi-spark',
55 style: { display: 'block', overflow: 'visible' }
56 },
57 el('path', { d: fillPath, fill: fill, stroke: 'none' }),
58 el('polyline', {
59 points: points,
60 fill: 'none',
61 stroke: color,
62 strokeWidth: 2,
63 strokeLinecap: 'round',
64 strokeLinejoin: 'round'
65 })
66 );
67 }
68
69 /* ── Single tile preview ───────────────────────────────────────── */
70 function TilePreview(tile, a) {
71 var dir = tile.direction || 'up';
72 return el('div', {
73 className: 'bkbg-kpi-tile',
74 style: {
75 background: a.cardBg,
76 borderRadius: a.cardRadius + 'px',
77 padding: a.cardPadding + 'px',
78 border: '1px solid ' + (a.borderColor || 'transparent'),
79 boxShadow: '0 2px 16px ' + (a.cardShadow || 'rgba(0,0,0,0.08)')
80 }
81 },
82 /* top row: icon + trend */
83 el('div', { className: 'bkbg-kpi-top' },
84 a.showIcon !== false && el('span', {
85 className: 'bkbg-kpi-icon',
86 style: { fontSize: a.iconSize + 'px' }
87 }, (tile.iconType || 'custom-char') !== 'custom-char' && IP() ? IP().buildEditorIcon(tile.iconType, tile.icon, tile.iconDashicon, tile.iconImageUrl, tile.iconDashiconColor) : (tile.icon || '📊')),
88 a.showTrend !== false && el('span', {
89 className: 'bkbg-kpi-trend ' + trendClass(dir)
90 }, trendIcon(dir) + ' ' + (tile.trend || ''))
91 ),
92 /* value */
93 el('div', {
94 className: 'bkbg-kpi-value',
95 style: { color: a.valueColor }
96 }, tile.value || ''),
97 /* label */
98 el('div', {
99 className: 'bkbg-kpi-label',
100 style: { color: a.labelColor }
101 }, tile.label || ''),
102 /* sparkline */
103 a.showSparkline !== false && SparklineEl(
104 tile.sparkline, a.sparklineColor, a.sparklineFill,
105 a.sparklineWidth || 120, a.sparklineHeight || 48
106 )
107 );
108 }
109
110 registerBlockType('blockenberg/kpi-tiles', {
111 title: __('KPI Tiles'),
112 icon: 'chart-bar',
113 category: 'bkbg-business',
114
115 edit: function (props) {
116 var attr = props.attributes;
117 var setAttr = props.setAttributes;
118 var bp = useBlockProps((function () {
119 var _tvFn = getTypoCssVars();
120 var s = {};
121 if (_tvFn) {
122 Object.assign(s, _tvFn(attr.valueTypo, '--bkbg-kpi-v-'));
123 Object.assign(s, _tvFn(attr.labelTypo, '--bkbg-kpi-l-'));
124 Object.assign(s, _tvFn(attr.trendTypo, '--bkbg-kpi-tr-'));
125 }
126 return { className: 'bkbg-kpi-wrap', style: s };
127 })());
128
129 function updateTile(idx, field, val) {
130 var tiles = attr.tiles.map(function (t, i) {
131 return i === idx ? Object.assign({}, t, { [field]: val }) : t;
132 });
133 setAttr({ tiles: tiles });
134 }
135 function addTile() {
136 setAttr({ tiles: attr.tiles.concat([{
137 label: 'New Metric', value: '0', trend: '+0%',
138 direction: 'up', sparkline: '10,20,15,30,25,40', icon: '📊',
139 iconType: 'custom-char', iconDashicon: '', iconImageUrl: ''
140 }]) });
141 }
142 function removeTile(idx) {
143 setAttr({ tiles: attr.tiles.filter(function (_, i) { return i !== idx; }) });
144 }
145
146 var inspector = el(InspectorControls, {},
147 /* Tiles management */
148 el(PanelBody, { title: __('Tiles'), initialOpen: true },
149 attr.tiles.map(function (tile, idx) {
150 return el(PanelBody, {
151 key: idx,
152 title: (tile.label || 'Tile ' + (idx + 1)),
153 initialOpen: false
154 },
155 IP() && el(IP().IconPickerControl, {
156 iconType: tile.iconType || 'custom-char',
157 customChar: tile.icon || '',
158 dashicon: tile.iconDashicon || '',
159 imageUrl: tile.iconImageUrl || '',
160 onChangeType: function (v) { updateTile(idx, 'iconType', v); },
161 onChangeChar: function (v) { updateTile(idx, 'icon', v); },
162 onChangeDashicon: function (v) { updateTile(idx, 'iconDashicon', v); },
163 onChangeImageUrl: function (v) { updateTile(idx, 'iconImageUrl', v); }
164 }),
165 el(TextControl, { label: __('Label'), value: tile.label || '',
166 onChange: function (v) { updateTile(idx, 'label', v); },
167 __nextHasNoMarginBottom: true }),
168 el(TextControl, { label: __('Value'), value: tile.value || '',
169 onChange: function (v) { updateTile(idx, 'value', v); },
170 __nextHasNoMarginBottom: true }),
171 el(TextControl, { label: __('Trend (e.g. +18%)'), value: tile.trend || '',
172 onChange: function (v) { updateTile(idx, 'trend', v); },
173 __nextHasNoMarginBottom: true }),
174 el(SelectControl, {
175 label: __('Trend Direction'),
176 value: tile.direction || 'up',
177 options: [
178 { label: 'Up (good)', value: 'up' },
179 { label: 'Down (bad)', value: 'down' },
180 { label: 'Down (good)', value: 'down-good' },
181 { label: 'Neutral', value: 'neutral' }
182 ],
183 onChange: function (v) { updateTile(idx, 'direction', v); },
184 __nextHasNoMarginBottom: true
185 }),
186 el(TextControl, {
187 label: __('Sparkline data (comma-separated numbers)'),
188 value: tile.sparkline || '',
189 onChange: function (v) { updateTile(idx, 'sparkline', v); },
190 __nextHasNoMarginBottom: true
191 }),
192 attr.tiles.length > 1 && el(Button, {
193 isDestructive: true, variant: 'secondary',
194 onClick: function () { removeTile(idx); },
195 style: { marginTop: '8px' }
196 }, __('Remove Tile'))
197 );
198 }),
199 el(Button, {
200 variant: 'primary', onClick: addTile,
201 style: { marginTop: '8px', width: '100%' }
202 }, __('+ Add Tile'))
203 ),
204
205 /* Layout */
206 el(PanelBody, { title: __('Layout'), initialOpen: false },
207 el(SelectControl, {
208 label: __('Columns'),
209 value: String(attr.columns),
210 options: [
211 { label: '2', value: '2' },
212 { label: '3', value: '3' },
213 { label: '4', value: '4' }
214 ],
215 onChange: function (v) { setAttr({ columns: Number(v) }); },
216 __nextHasNoMarginBottom: true
217 }),
218 el(RangeControl, { label: __('Card Radius (px)'), value: attr.cardRadius,
219 min: 0, max: 32,
220 onChange: function (v) { setAttr({ cardRadius: v }); },
221 __nextHasNoMarginBottom: true }),
222 el(RangeControl, { label: __('Card Padding (px)'), value: attr.cardPadding,
223 min: 12, max: 48,
224 onChange: function (v) { setAttr({ cardPadding: v }); },
225 __nextHasNoMarginBottom: true })
226 ),
227
228 /* Display */
229 el(PanelBody, { title: __('Display Options'), initialOpen: false },
230 el(ToggleControl, { label: __('Show Sparkline'), checked: attr.showSparkline,
231 onChange: function (v) { setAttr({ showSparkline: v }); },
232 __nextHasNoMarginBottom: true }),
233 el(ToggleControl, { label: __('Show Icon'), checked: attr.showIcon,
234 onChange: function (v) { setAttr({ showIcon: v }); },
235 __nextHasNoMarginBottom: true }),
236 el(ToggleControl, { label: __('Show Trend'), checked: attr.showTrend,
237 onChange: function (v) { setAttr({ showTrend: v }); },
238 __nextHasNoMarginBottom: true }),
239 el(ToggleControl, { label: __('Animate Numbers on Scroll'), checked: attr.animateNumbers,
240 onChange: function (v) { setAttr({ animateNumbers: v }); },
241 __nextHasNoMarginBottom: true }),
242 el(RangeControl, { label: __('Icon Size'), value: attr.iconSize,
243 min: 18, max: 56,
244 onChange: function (v) { setAttr({ iconSize: v }); },
245 __nextHasNoMarginBottom: true }),
246 el(RangeControl, { label: __('Sparkline Height (px)'), value: attr.sparklineHeight,
247 min: 24, max: 96,
248 onChange: function (v) { setAttr({ sparklineHeight: v }); },
249 __nextHasNoMarginBottom: true }),
250 el(RangeControl, { label: __('Sparkline Width (px)'), value: attr.sparklineWidth,
251 min: 60, max: 240,
252 onChange: function (v) { setAttr({ sparklineWidth: v }); },
253 __nextHasNoMarginBottom: true })
254 ),
255
256 /* Colors */
257
258 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
259 getTypoControl() && el(getTypoControl(), { label: __('Value'), value: attr.valueTypo || {}, onChange: function (v) { setAttr({ valueTypo: v }); } }),
260 getTypoControl() && el(getTypoControl(), { label: __('Label'), value: attr.labelTypo || {}, onChange: function (v) { setAttr({ labelTypo: v }); } }),
261 getTypoControl() && el(getTypoControl(), { label: __('Trend'), value: attr.trendTypo || {}, onChange: function (v) { setAttr({ trendTypo: v }); } })
262 ),
263 el(PanelColorSettings, {
264 title: __('Colors'),
265 initialOpen: false,
266 colorSettings: [
267 { label: __('Section BG'), value: attr.sectionBg,
268 onChange: function (v) { setAttr({ sectionBg: v || 'transparent' }); } },
269 { label: __('Card BG'), value: attr.cardBg,
270 onChange: function (v) { setAttr({ cardBg: v || '#ffffff' }); } },
271 { label: __('Card Shadow'), value: attr.cardShadow,
272 onChange: function (v) { setAttr({ cardShadow: v || 'rgba(0,0,0,0.08)' }); } },
273 { label: __('Card Border'), value: attr.borderColor,
274 onChange: function (v) { setAttr({ borderColor: v || 'transparent' }); } },
275 { label: __('Value Color'), value: attr.valueColor,
276 onChange: function (v) { setAttr({ valueColor: v || '#0f172a' }); } },
277 { label: __('Label Color'), value: attr.labelColor,
278 onChange: function (v) { setAttr({ labelColor: v || '#64748b' }); } },
279 { label: __('Up Trend Color'), value: attr.upColor,
280 onChange: function (v) { setAttr({ upColor: v || '#10b981' }); } },
281 { label: __('Down Trend Color'), value: attr.downColor,
282 onChange: function (v) { setAttr({ downColor: v || '#ef4444' }); } },
283 { label: __('Down (Good) Color'), value: attr.downGoodColor,
284 onChange: function (v) { setAttr({ downGoodColor: v || '#10b981' }); } },
285 { label: __('Sparkline Line'), value: attr.sparklineColor,
286 onChange: function (v) { setAttr({ sparklineColor: v || '#6366f1' }); } },
287 { label: __('Sparkline Fill'), value: attr.sparklineFill,
288 onChange: function (v) { setAttr({ sparklineFill: v || 'rgba(99,102,241,0.12)' }); } }
289 ]
290 })
291 );
292
293 var gridStyle = {
294 display: 'grid',
295 gridTemplateColumns: 'repeat(' + attr.columns + ', 1fr)',
296 gap: '20px',
297 background: attr.sectionBg || 'transparent',
298 padding: '8px 0'
299 };
300
301 return el(wp.element.Fragment, {}, inspector,
302 el('div', bp,
303 el('div', { className: 'bkbg-kpi-grid', style: gridStyle },
304 attr.tiles.map(function (tile, i) {
305 return el('div', { key: i }, TilePreview(tile, attr));
306 })
307 )
308 )
309 );
310 },
311
312 save: function (props) {
313 var attr = props.attributes;
314 var bp = useBlockProps.save();
315 var opts = {
316 tiles: attr.tiles,
317 columns: attr.columns,
318 showSparkline: attr.showSparkline,
319 showIcon: attr.showIcon,
320 showTrend: attr.showTrend,
321 animateNumbers: attr.animateNumbers,
322 cardRadius: attr.cardRadius,
323 cardPadding: attr.cardPadding,
324 sparklineHeight: attr.sparklineHeight,
325 sparklineWidth: attr.sparklineWidth,
326 valueSize: attr.valueSize,
327 labelSize: attr.labelSize,
328 trendSize: attr.trendSize,
329 iconSize: attr.iconSize,
330 cardBg: attr.cardBg,
331 cardShadow: attr.cardShadow,
332 valueColor: attr.valueColor,
333 labelColor: attr.labelColor,
334 upColor: attr.upColor,
335 downColor: attr.downColor,
336 downGoodColor: attr.downGoodColor,
337 sparklineColor: attr.sparklineColor,
338 sparklineFill: attr.sparklineFill,
339 sectionBg: attr.sectionBg,
340 borderColor: attr.borderColor,
341 valueTypo: attr.valueTypo,
342 labelTypo: attr.labelTypo,
343 trendTypo: attr.trendTypo
344 };
345 return el('div', bp,
346 el('div', { className: 'bkbg-kpi-app', 'data-opts': JSON.stringify(opts) })
347 );
348 }
349 });
350 }() );
351