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 / metric-comparison / index.js

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

229 lines 15.4 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 var _tc, _tv;
9 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
10 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
11
12 // ── Formatters ────────────────────────────────────────────────────────────
13 function fmtValue( v, fmt, unit ) {
14 const n = parseFloat( v ) || 0;
15 let str;
16 if ( fmt === 'currency' ) {
17 str = unit + n.toLocaleString( 'en-US', { maximumFractionDigits: 0 } );
18 } else if ( fmt === 'percent' ) {
19 str = n.toFixed( 1 ) + '%';
20 } else {
21 str = n.toLocaleString( 'en-US', { maximumFractionDigits: 2 } );
22 if ( unit ) str += ' ' + unit;
23 }
24 return str;
25 }
26
27 function changePct( before, after ) {
28 const b = parseFloat( before ) || 0;
29 const a = parseFloat( after ) || 0;
30 if ( b === 0 ) return null;
31 return ( ( a - b ) / Math.abs( b ) ) * 100;
32 }
33
34 function barFraction( before, after ) {
35 const b = Math.abs( parseFloat( before ) ) || 0;
36 const a = Math.abs( parseFloat( after ) ) || 0;
37 const mx = Math.max( b, a ) || 1;
38 return { bFrac: b / mx, aFrac: a / mx };
39 }
40
41 // ── One card ─────────────────────────────────────────────────────────────
42 function MetricCard( props ) {
43 const { m, a, inlineTypo } = props;
44 const pct = changePct( m.valueBefore, m.valueAfter );
45 const isPos = m.higherIsBetter ? pct >= 0 : pct <= 0;
46 const isNeg = ! isPos;
47 const isNull = pct === null;
48 const chgColor = isNull ? a.neutralColor : isPos ? a.posColor : a.negColor;
49 const arrow = pct === null ? '' : pct > 0 ? '' : pct < 0 ? '' : '';
50 const { bFrac, aFrac } = barFraction( m.valueBefore, m.valueAfter );
51 const cardStyle = {
52 background: a.cardBg,
53 borderRadius: a.borderRadius + 'px',
54 padding: a.cardPadding + 'px',
55 boxSizing: 'border-box',
56 display: 'flex',
57 flexDirection: 'column',
58 gap: 6,
59 };
60 var labelSt = { color: a.labelColor };
61 if (inlineTypo) { labelSt.fontSize = (a.labelFontSize || 13); labelSt.fontWeight = 600; labelSt.lineHeight = (a.lineHeight || 1.3); labelSt.textTransform = 'uppercase'; labelSt.letterSpacing = '0.05em'; }
62 var valueSt = { color: a.textColor };
63 if (inlineTypo) { valueSt.fontSize = (a.valueFontSize || 32); valueSt.fontWeight = (a.titleFontWeight || 700); valueSt.lineHeight = (a.lineHeight || 1.3); }
64
65 return el( 'div', { className: 'bkbg-mc-card', style: cardStyle },
66 // Label
67 el( 'div', { className: 'bkbg-mc-label', style: labelSt }, m.label ),
68
69 // Main value (after)
70 el( 'div', { className: 'bkbg-mc-value', style: valueSt },
71 fmtValue( m.valueAfter, m.format, m.unit )
72 ),
73
74 // Change row
75 a.showChange && el( 'div', { className: 'bkbg-mc-change', style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 13 } },
76 a.showArrows && el( 'span', { style: { color: chgColor, fontWeight: 700, fontSize: 15 } }, arrow ),
77 el( 'span', { style: { color: chgColor, fontWeight: 600 } }, pct !== null ? Math.abs( pct ).toFixed( 1 ) + '%' : '' ),
78 el( 'span', { style: { color: a.labelColor, fontSize: 11 } }, `vs ${ a.beforeLabel }: ${ fmtValue( m.valueBefore, m.format, m.unit ) }` ),
79 ),
80
81 // Mini bar
82 a.showBar && el( 'div', { className: 'bkbg-mc-bars', style: { marginTop: 6, display: 'flex', flexDirection: 'column', gap: 3 } },
83 el( 'div', { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 10, color: a.labelColor } },
84 el( 'span', { style: { width: 24, textAlign: 'right', flexShrink: 0 } }, a.beforeLabel ),
85 el( 'div', { style: { flex: 1, height: 6, background: '#e5e7eb', borderRadius: 3, overflow: 'hidden' } },
86 el( 'div', { style: { width: ( bFrac * 100 ) + '%', height: '100%', background: a.neutralColor, borderRadius: 3 } } )
87 ),
88 ),
89 el( 'div', { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 10, color: a.labelColor } },
90 el( 'span', { style: { width: 24, textAlign: 'right', flexShrink: 0 } }, a.afterLabel ),
91 el( 'div', { style: { flex: 1, height: 6, background: '#e5e7eb', borderRadius: 3, overflow: 'hidden' } },
92 el( 'div', { style: { width: ( aFrac * 100 ) + '%', height: '100%', background: chgColor, borderRadius: 3 } } )
93 ),
94 ),
95 ),
96 );
97 }
98
99 // ── Grid renderer ─────────────────────────────────────────────────────────
100 function renderGrid( a, opts ) {
101 opts = opts || {};
102 const metrics = a.metrics || [];
103 return el( 'div', {
104 className: 'bkbg-mc-grid',
105 style: {
106 display: 'grid',
107 gridTemplateColumns: `repeat(${ a.columns }, 1fr)`,
108 gap: '16px',
109 }
110 }, ...metrics.map( ( m, i ) => el( MetricCard, { key: i, m, a, inlineTypo: opts.inlineTypo } ) ) );
111 }
112
113 function updMetric( setAttributes, metrics, mi, field, val ) {
114 setAttributes( { metrics: metrics.map( ( m, i ) => i === mi ? { ...m, [field]: val } : m ) } );
115 }
116
117 registerBlockType( 'blockenberg/metric-comparison', {
118 deprecated: [{
119 save: function ( { attributes: a } ) {
120 var blockProps = useBlockProps.save( { className: 'bkbg-mc-wrap' } );
121 return el( 'div', blockProps,
122 el( 'div', { className: 'bkbg-mc-inner', style: { background: a.bgColor, padding: '16px', borderRadius: 8, boxSizing: 'border-box' } },
123 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-mc-title', style: { color: a.titleColor, margin: '0 0 16px', fontSize: (a.titleFontSize || 20) + 'px', fontWeight: (a.titleFontWeight || 700) } }, a.title ) : null,
124 renderGrid( a, { inlineTypo: true } ),
125 ),
126 );
127 },
128 }],
129 edit: function ( props ) {
130 const { attributes: a, setAttributes } = props;
131 var blockProps = useBlockProps((function () {
132 var _tvf = getTypoCssVars();
133 var s = {};
134 Object.assign(s, _tvf(a.titleTypo, '--bkbg-mc-tt-'));
135 Object.assign(s, _tvf(a.labelTypo, '--bkbg-mc-lb-'));
136 Object.assign(s, _tvf(a.valueTypo, '--bkbg-mc-vl-'));
137 return { className: 'bkbg-mc-wrap', style: s };
138 })());
139
140 return el( 'div', blockProps,
141 el( InspectorControls, {},
142
143 el( PanelBody, { title: __( 'Settings', 'blockenberg' ), initialOpen: true },
144 el( TextControl, { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ),
145 el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ), checked: a.showTitle, onChange: v => setAttributes( { showTitle: v } ) } ),
146 el( TextControl, { label: __( 'Before Label', 'blockenberg' ), value: a.beforeLabel, onChange: v => setAttributes( { beforeLabel: v } ) } ),
147 el( TextControl, { label: __( 'After Label', 'blockenberg' ), value: a.afterLabel, onChange: v => setAttributes( { afterLabel: v } ) } ),
148 el( RangeControl, { label: __( 'Columns', 'blockenberg' ), value: a.columns, onChange: v => setAttributes( { columns: v } ), min: 1, max: 6 } ),
149 el( ToggleControl, { label: __( 'Show Change', 'blockenberg' ), checked: a.showChange, onChange: v => setAttributes( { showChange: v } ) } ),
150 el( ToggleControl, { label: __( 'Show Arrows', 'blockenberg' ), checked: a.showArrows, onChange: v => setAttributes( { showArrows: v } ) } ),
151 el( ToggleControl, { label: __( 'Show Bar', 'blockenberg' ), checked: a.showBar, onChange: v => setAttributes( { showBar: v } ) } ),
152 ),
153
154 el( PanelBody, { title: __( 'Card Style', 'blockenberg' ), initialOpen: false },
155 el( RangeControl, { label: __( 'Border Radius', 'blockenberg' ), value: a.borderRadius, onChange: v => setAttributes( { borderRadius: v } ), min: 0, max: 30 } ),
156 el( RangeControl, { label: __( 'Padding', 'blockenberg' ), value: a.cardPadding, onChange: v => setAttributes( { cardPadding: v } ), min: 8, max: 48 } ),
157 ),
158
159 el( PanelColorSettings, {
160 title: __( 'Colors', 'blockenberg' ), initialOpen: false,
161 colorSettings: [
162 { label: __( 'Wrapper BG', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#ffffff' } ) },
163 { label: __( 'Card BG', 'blockenberg' ), value: a.cardBg, onChange: v => setAttributes( { cardBg: v || '#f9fafb' } ) },
164 { label: __( 'Positive', 'blockenberg' ), value: a.posColor, onChange: v => setAttributes( { posColor: v || '#10b981' } ) },
165 { label: __( 'Negative', 'blockenberg' ), value: a.negColor, onChange: v => setAttributes( { negColor: v || '#ef4444' } ) },
166 { label: __( 'Neutral', 'blockenberg' ), value: a.neutralColor, onChange: v => setAttributes( { neutralColor: v || '#6b7280' } ) },
167 { label: __( 'Title', 'blockenberg' ), value: a.titleColor, onChange: v => setAttributes( { titleColor: v || '#111827' } ) },
168 { label: __( 'Value Text', 'blockenberg' ), value: a.textColor, onChange: v => setAttributes( { textColor: v || '#111827' } ) },
169 { label: __( 'Labels', 'blockenberg' ), value: a.labelColor, onChange: v => setAttributes( { labelColor: v || '#6b7280' } ) },
170 ]
171 } ),
172
173 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
174 el( getTypoControl(), { label: __( 'Title', 'blockenberg' ), value: a.titleTypo, onChange: function (v) { setAttributes({ titleTypo: v }); } }),
175 el( getTypoControl(), { label: __( 'Label', 'blockenberg' ), value: a.labelTypo, onChange: function (v) { setAttributes({ labelTypo: v }); } }),
176 el( getTypoControl(), { label: __( 'Value', 'blockenberg' ), value: a.valueTypo, onChange: function (v) { setAttributes({ valueTypo: v }); } })
177 ),
178
179 el( PanelBody, { title: __( 'Metrics', 'blockenberg' ), initialOpen: false },
180 el( Button, { variant: 'secondary', style: { marginBottom: 10 }, onClick: () => setAttributes( { metrics: [ ...( a.metrics || [] ), { label: 'New Metric', valueBefore: 100, valueAfter: 120, unit: '', format: 'number', higherIsBetter: true } ] } ) }, __( '+ Add Metric', 'blockenberg' ) ),
181 a.metrics.map( ( m, mi ) =>
182 el( PanelBody, { key: mi, title: m.label, initialOpen: false },
183 el( TextControl, { label: __( 'Label', 'blockenberg' ), value: m.label, onChange: v => updMetric( setAttributes, a.metrics, mi, 'label', v ) } ),
184 el( TextControl, { label: `${ a.beforeLabel } value`, value: String( m.valueBefore ), type: 'number', onChange: v => updMetric( setAttributes, a.metrics, mi, 'valueBefore', parseFloat( v ) || 0 ) } ),
185 el( TextControl, { label: `${ a.afterLabel } value`, value: String( m.valueAfter ), type: 'number', onChange: v => updMetric( setAttributes, a.metrics, mi, 'valueAfter', parseFloat( v ) || 0 ) } ),
186 el( TextControl, { label: __( 'Unit', 'blockenberg' ), value: m.unit, onChange: v => updMetric( setAttributes, a.metrics, mi, 'unit', v ) } ),
187 el( SelectControl, {
188 label: __( 'Format', 'blockenberg' ),
189 value: m.format,
190 options: [
191 { label: __( 'Number', 'blockenberg' ), value: 'number' },
192 { label: __( 'Currency', 'blockenberg' ), value: 'currency' },
193 { label: __( 'Percent', 'blockenberg' ), value: 'percent' },
194 ],
195 onChange: v => updMetric( setAttributes, a.metrics, mi, 'format', v ),
196 } ),
197 el( ToggleControl, { label: __( 'Higher is better', 'blockenberg' ), checked: m.higherIsBetter, onChange: v => updMetric( setAttributes, a.metrics, mi, 'higherIsBetter', v ) } ),
198 el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { metrics: a.metrics.filter( ( _, x ) => x !== mi ) } ) }, __( 'Remove', 'blockenberg' ) ),
199 )
200 ),
201 ),
202 ),
203
204 el( 'div', { className: 'bkbg-mc-inner', style: { background: a.bgColor, padding: '16px', borderRadius: 8, boxSizing: 'border-box' } },
205 a.showTitle && a.title && el( 'h3', { className: 'bkbg-mc-title', style: { color: a.titleColor, margin: '0 0 16px' } }, a.title ),
206 renderGrid( a ),
207 ),
208 );
209 },
210
211 save: function ( { attributes: a } ) {
212 var blockProps = useBlockProps.save((function () {
213 var _tvf = getTypoCssVars();
214 var s = {};
215 Object.assign(s, _tvf(a.titleTypo, '--bkbg-mc-tt-'));
216 Object.assign(s, _tvf(a.labelTypo, '--bkbg-mc-lb-'));
217 Object.assign(s, _tvf(a.valueTypo, '--bkbg-mc-vl-'));
218 return { className: 'bkbg-mc-wrap', style: s };
219 })());
220 return el( 'div', blockProps,
221 el( 'div', { className: 'bkbg-mc-inner', style: { background: a.bgColor, padding: '16px', borderRadius: 8, boxSizing: 'border-box' } },
222 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-mc-title', style: { color: a.titleColor, margin: '0 0 16px' } }, a.title ) : null,
223 renderGrid( a ),
224 ),
225 );
226 },
227 } );
228 }() );
229