PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.10
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.10
2.0.14 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 / calorie-calculator / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.10, at blocks/calorie-calculator/index.js

292 lines 21.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 useState = wp.element.useState;
4 var Fragment = wp.element.Fragment;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var __ = wp.i18n.__;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var RangeControl = wp.components.RangeControl;
12 var SelectControl = wp.components.SelectControl;
13 var TextControl = wp.components.TextControl;
14 var ToggleControl = wp.components.ToggleControl;
15
16 // ── Typography helpers ──────────────────────────────────────────────────
17 function getTypographyControl() {
18 return (window.bkbgTypographyControl || function () { return null; });
19 }
20
21 function _tv(typo, prefix) {
22 if (!typo) return {};
23 var s = {};
24 if (typo.family) s[prefix + 'font-family'] = "'" + typo.family + "', sans-serif";
25 if (typo.weight) s[prefix + 'font-weight'] = typo.weight;
26 if (typo.transform) s[prefix + 'text-transform'] = typo.transform;
27 if (typo.style) s[prefix + 'font-style'] = typo.style;
28 if (typo.decoration) s[prefix + 'text-decoration'] = typo.decoration;
29 var su = typo.sizeUnit || 'px';
30 if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) s[prefix + 'font-size-d'] = typo.sizeDesktop + su;
31 if (typo.sizeTablet !== '' && typo.sizeTablet != null) s[prefix + 'font-size-t'] = typo.sizeTablet + su;
32 if (typo.sizeMobile !== '' && typo.sizeMobile != null) s[prefix + 'font-size-m'] = typo.sizeMobile + su;
33 var lhu = typo.lineHeightUnit || '';
34 if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) s[prefix + 'line-height-d'] = typo.lineHeightDesktop + lhu;
35 if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) s[prefix + 'line-height-t'] = typo.lineHeightTablet + lhu;
36 if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) s[prefix + 'line-height-m'] = typo.lineHeightMobile + lhu;
37 var lsu = typo.letterSpacingUnit || 'px';
38 if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) s[prefix + 'letter-spacing-d'] = typo.letterSpacingDesktop + lsu;
39 if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) s[prefix + 'letter-spacing-t'] = typo.letterSpacingTablet + lsu;
40 if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) s[prefix + 'letter-spacing-m'] = typo.letterSpacingMobile + lsu;
41 var wsu = typo.wordSpacingUnit || 'px';
42 if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) s[prefix + 'word-spacing-d'] = typo.wordSpacingDesktop + wsu;
43 if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) s[prefix + 'word-spacing-t'] = typo.wordSpacingTablet + wsu;
44 if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) s[prefix + 'word-spacing-m'] = typo.wordSpacingMobile + wsu;
45 return s;
46 }
47
48 var ACTIVITY_OPTIONS = [
49 { value: 'sedentary', label: 'Sedentary (little/no exercise)', mult: 1.200 },
50 { value: 'light', label: 'Lightly active (1–3 days/week)', mult: 1.375 },
51 { value: 'moderate', label: 'Moderately active (3–5 days/week)', mult: 1.550 },
52 { value: 'very', label: 'Very active (6–7 days/week)', mult: 1.725 },
53 { value: 'extra', label: 'Extra active (physical job + hard ex)', mult: 1.900 },
54 ];
55
56 var GOAL_OPTIONS = [
57 { value: 'lose2', label: 'Lose weight fast (−1000 kcal)', adj: -1000 },
58 { value: 'lose1', label: 'Lose weight (−500 kcal)', adj: -500 },
59 { value: 'maintain',label: 'Maintain weight', adj: 0 },
60 { value: 'gain1', label: 'Gain weight (+300 kcal)', adj: +300 },
61 { value: 'gain2', label: 'Build muscle (+500 kcal)', adj: +500 },
62 ];
63
64 function calcBMR(gender, weight, height, age) {
65 // Mifflin-St Jeor
66 var base = 10 * weight + 6.25 * height - 5 * age;
67 return gender === 'female' ? base - 161 : base + 5;
68 }
69
70 function actMult(activity) {
71 var opt = ACTIVITY_OPTIONS.find(function (o) { return o.value === activity; });
72 return opt ? opt.mult : 1.55;
73 }
74
75 function CalcPreview(props) {
76 var a = props.attributes;
77 var setAttr = props.setAttributes;
78
79 var _s = useState({ gender: a.defaultGender, age: a.defaultAge, weight: a.defaultWeight, height: a.defaultHeight, activity: a.defaultActivity, units: a.defaultUnits, goal: 'maintain' });
80 var state = _s[0];
81 var setState= _s[1];
82
83 function set(key, val) { setState(function (prev) { var o = Object.assign({}, prev); o[key] = val; return o; }); }
84
85 /* Convert to metric for calculation */
86 var weightKg = state.units === 'imperial' ? state.weight * 0.453592 : state.weight;
87 var heightCm = state.units === 'imperial' ? state.height * 2.54 : state.height;
88 var bmr = Math.round(calcBMR(state.gender, weightKg, heightCm, state.age));
89 var tdee = Math.round(bmr * actMult(state.activity));
90 var goalAdj = (GOAL_OPTIONS.find(function (o) { return o.value === state.goal; }) || { adj: 0 }).adj;
91 var target = tdee + goalAdj;
92 var proteinG = Math.round(target * (a.proteinPct / 100) / 4);
93 var carbG = Math.round(target * (a.carbPct / 100) / 4);
94 var fatG = Math.round(target * (a.fatPct / 100) / 9);
95
96 var accent = a.accentColor || '#6c3fb5';
97 var maxW = (a.maxWidth || 620) + 'px';
98 var cRadius = (a.cardRadius || 16) + 'px';
99 var iRadius = (a.inputRadius || 8) + 'px';
100 var ptop = (a.paddingTop || 60) + 'px';
101 var pbot = (a.paddingBottom || 60) + 'px';
102 var tdeeSize = (a.tdeeSize || 52) + 'px';
103
104 /* Shared input style */
105 var inpStyle = { width: '100%', padding: '10px 12px', border: '1.5px solid #e5e7eb', borderRadius: iRadius, fontSize: '15px', boxSizing: 'border-box', outline: 'none', background: '#fff', color: a.labelColor || '#374151' };
106 var selStyle = Object.assign({}, inpStyle);
107 var lblStyle = { fontSize: '13px', fontWeight: 600, color: a.labelColor || '#374151', display: 'block', marginBottom: '4px' };
108 var fldStyle = { marginBottom: '14px' };
109
110 function field(label, input) {
111 return el('div', { style: fldStyle },
112 el('label', { style: lblStyle }, label),
113 input
114 );
115 }
116
117 /* Gender row */
118 var genderRow = el('div', { style: { display:'flex', gap:'10px', marginBottom:'14px' } },
119 ['male','female'].map(function (g) {
120 var active = state.gender === g;
121 return el('button', {
122 key: g,
123 onClick: function () { set('gender', g); },
124 style: { flex:1, padding:'10px', border:'2px solid '+(active? accent:'#e5e7eb'), borderRadius: iRadius, background: active? accent: '#fff', color: active?'#fff': (a.labelColor||'#374151'), fontWeight:600, cursor:'pointer', fontSize:'14px' }
125 }, g === 'male' ? '♂ Male' : '♀ Female');
126 })
127 );
128
129 /* Units row */
130 var unitsRow = el('div', { style: { display:'flex', gap:'10px', marginBottom:'14px' } },
131 ['metric','imperial'].map(function (u) {
132 var active = state.units === u;
133 return el('button', {
134 key: u,
135 onClick: function () { set('units', u); },
136 style: { flex:1, padding:'8px', border:'2px solid '+(active? accent:'#e5e7eb'), borderRadius: iRadius, background: active? accent:'#fff', color: active?'#fff':(a.labelColor||'#374151'), fontWeight:600, cursor:'pointer', fontSize:'13px' }
137 }, u === 'metric' ? 'Metric (kg/cm)' : 'Imperial (lbs/in)');
138 })
139 );
140
141 var weightUnit = state.units === 'imperial' ? 'lbs' : 'kg';
142 var heightUnit = state.units === 'imperial' ? 'in' : 'cm';
143
144 /* Macro bar */
145 var macroBar = el('div', { style: { display:'flex', borderRadius:'8px', overflow:'hidden', height:'12px', marginBottom:'16px' } },
146 el('div', { style: { flex: a.proteinPct, background: a.proColor || '#ef4444' } }),
147 el('div', { style: { flex: a.carbPct, background: a.carbColor || '#f59e0b' } }),
148 el('div', { style: { flex: a.fatPct, background: a.fatColor || '#3b82f6' } })
149 );
150
151 function macroCard(label, grams, pct, color) {
152 return el('div', { style: { textAlign:'center', background: '#fff', borderRadius:'10px', padding:'12px 8px', border:'1.5px solid #e5e7eb' } },
153 el('div', { style: { fontSize:'11px', textTransform:'uppercase', letterSpacing:'0.05em', color:'#9ca3af', marginBottom:'4px' } }, label),
154 el('div', { style: { fontSize:'24px', fontWeight:800, color: color, lineHeight:1 } }, grams + 'g'),
155 el('div', { style: { fontSize:'12px', color:'#9ca3af', marginTop:'2px' } }, pct + '%')
156 );
157 }
158
159 return el('div', { style: Object.assign({ background: a.cardBg||'#fff', borderRadius: cRadius, maxWidth: maxW, margin: '0 auto', paddingTop: ptop, paddingBottom: pbot, paddingLeft:'32px', paddingRight:'32px' }, _tv(a.typoTitle, '--bkbg-cal-tt-'), _tv(a.typoSub, '--bkbg-cal-ts-')) },
160 /* Title / subtitle */
161 a.showTitle !== false && a.title && el('h2', { className: 'bkbg-cal-title', style: { color: a.titleColor||'#1e1b4b', textAlign:'center', marginTop:0, marginBottom:'8px' } }, a.title),
162 a.showSubtitle !== false && a.subtitle && el('p', { className: 'bkbg-cal-subtitle', style: { color: a.subtitleColor||'#6b7280', textAlign:'center', marginTop:0, marginBottom:'24px' } }, a.subtitle),
163
164 /* Units toggle */
165 unitsRow,
166
167 /* Inputs */
168 el('label', { style: lblStyle }, 'Gender'),
169 genderRow,
170 field('Age (years)', el('input', { type:'number', min:10, max:100, value: state.age, style: inpStyle, onChange: function (e) { set('age', parseInt(e.target.value)||30); } })),
171 field('Height (' + heightUnit + ')', el('input', { type:'number', min:50, max:300, value: state.height, style: inpStyle, onChange: function (e) { set('height', parseFloat(e.target.value)||170); } })),
172 field('Weight (' + weightUnit + ')', el('input', { type:'number', min:20, max:500, value: state.weight, style: inpStyle, onChange: function (e) { set('weight', parseFloat(e.target.value)||70); } })),
173 field('Activity level', el('select', { value: state.activity, style: selStyle, onChange: function (e) { set('activity', e.target.value); } },
174 ACTIVITY_OPTIONS.map(function (o) { return el('option', { key: o.value, value: o.value }, o.label); })
175 )),
176
177 /* Goal */
178 a.showGoalCalc !== false && field('Goal', el('select', { value: state.goal, style: selStyle, onChange: function (e) { set('goal', e.target.value); } },
179 GOAL_OPTIONS.map(function (o) { return el('option', { key: o.value, value: o.value }, o.label); })
180 )),
181
182 /* Result box */
183 el('div', { style: { background: a.resultBg||'#f5f3ff', border:'1.5px solid '+(a.resultBorder||'#ede9fe'), borderRadius: cRadius, padding:'24px', textAlign:'center', marginBottom: a.showMacros!==false?'20px':'0' } },
184 el('div', { style: { fontSize:'13px', color:'#9ca3af', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:'4px' } }, 'Daily Calories (TDEE)'),
185 el('div', { style: { fontSize: tdeeSize, fontWeight:800, color: a.tdeeColor||accent, lineHeight:1, marginBottom:'4px' } }, target.toLocaleString()),
186 el('div', { style: { fontSize:'13px', color:'#9ca3af' } }, 'kcal / day'),
187 a.showBMR !== false && el('div', { style: { marginTop:'12px', fontSize:'13px', color: a.bmrColor||'#8b5cf6' } },
188 'Basal Metabolic Rate (BMR): ' + bmr.toLocaleString() + ' kcal/day'
189 )
190 ),
191
192 /* Macros */
193 a.showMacros !== false && el(Fragment, null,
194 macroBar,
195 el('div', { style: { display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:'10px' } },
196 macroCard('Protein', proteinG, a.proteinPct, a.proColor||'#ef4444'),
197 macroCard('Carbs', carbG, a.carbPct, a.carbColor||'#f59e0b'),
198 macroCard('Fat', fatG, a.fatPct, a.fatColor||'#3b82f6')
199 )
200 )
201 );
202 }
203
204 registerBlockType('blockenberg/calorie-calculator', {
205 edit: function (props) {
206 var a = props.attributes;
207 var setAttr = props.setAttributes;
208 var blockProps = useBlockProps({ className: 'bkbg-calorie-calculator-block' });
209
210 return el('div', blockProps,
211 el(InspectorControls, null,
212 el(PanelBody, { title: __('Content', 'blockenberg'), initialOpen: true },
213 el(TextControl, { label: 'Title', value: a.title, onChange: function (v) { setAttr({ title: v }); } }),
214 el(ToggleControl, { label: 'Show title', checked: a.showTitle, onChange: function (v) { setAttr({ showTitle: v }); }, __nextHasNoMarginBottom: true }),
215 el(TextControl, { label: 'Subtitle', value: a.subtitle, onChange: function (v) { setAttr({ subtitle: v }); } }),
216 el(ToggleControl, { label: 'Show subtitle', checked: a.showSubtitle, onChange: function (v) { setAttr({ showSubtitle: v }); }, __nextHasNoMarginBottom: true }),
217 ),
218 el(PanelBody, { title: __('Defaults', 'blockenberg'), initialOpen: false },
219 el(SelectControl, { label: 'Default units', value: a.defaultUnits, options: [{label:'Metric',value:'metric'},{label:'Imperial',value:'imperial'}], onChange: function (v) { setAttr({ defaultUnits: v }); } }),
220 el(SelectControl, { label: 'Default gender', value: a.defaultGender, options: [{label:'Male',value:'male'},{label:'Female',value:'female'}], onChange: function (v) { setAttr({ defaultGender: v }); } }),
221 el(RangeControl, { label: 'Default age', value: a.defaultAge, min:10, max:100, onChange: function (v) { setAttr({ defaultAge: v }); } }),
222 el(RangeControl, { label: 'Default weight (metric kg)', value: a.defaultWeight, min:20, max:300, onChange: function (v) { setAttr({ defaultWeight: v }); } }),
223 el(RangeControl, { label: 'Default height (metric cm)', value: a.defaultHeight, min:100, max:250, onChange: function (v) { setAttr({ defaultHeight: v }); } }),
224 el(SelectControl, { label: 'Default activity', value: a.defaultActivity, options: ACTIVITY_OPTIONS.map(function(o){return{label:o.label,value:o.value};}), onChange: function (v) { setAttr({ defaultActivity: v }); } }),
225 ),
226 el(PanelBody, { title: __('Display', 'blockenberg'), initialOpen: false },
227 el(ToggleControl, { label: 'Show macros', checked: a.showMacros, onChange: function (v) { setAttr({ showMacros: v }); }, __nextHasNoMarginBottom: true }),
228 el(ToggleControl, { label: 'Show goal calc', checked: a.showGoalCalc, onChange: function (v) { setAttr({ showGoalCalc: v }); }, __nextHasNoMarginBottom: true }),
229 el(ToggleControl, { label: 'Show BMR', checked: a.showBMR, onChange: function (v) { setAttr({ showBMR: v }); }, __nextHasNoMarginBottom: true }),
230 ),
231 el(PanelBody, { title: __('Macro Ratios (%)', 'blockenberg'), initialOpen: false },
232 el(RangeControl, { label: 'Protein %', value: a.proteinPct, min:10, max:60, onChange: function (v) { setAttr({ proteinPct: v }); } }),
233 el(RangeControl, { label: 'Carbs %', value: a.carbPct, min:10, max:70, onChange: function (v) { setAttr({ carbPct: v }); } }),
234 el(RangeControl, { label: 'Fat %', value: a.fatPct, min:10, max:60, onChange: function (v) { setAttr({ fatPct: v }); } }),
235 ),
236
237 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
238 el( 'p', { style: { margin: '0 0 4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __( 'Title', 'blockenberg' ) ),
239 el( getTypographyControl(), {
240 label: __( 'Title Typography', 'blockenberg' ),
241 value: a.typoTitle,
242 onChange: function (v) { setAttr({ typoTitle: v }); }
243 }),
244 el( 'hr', {} ),
245 el( 'p', { style: { margin: '8px 0 4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __( 'Subtitle', 'blockenberg' ) ),
246 el( getTypographyControl(), {
247 label: __( 'Subtitle Typography', 'blockenberg' ),
248 value: a.typoSub,
249 onChange: function (v) { setAttr({ typoSub: v }); }
250 }),
251 el( 'hr', {} ),
252 el(RangeControl, { label: 'TDEE number size (px)', value: a.tdeeSize, min: 24, max: 80, onChange: function (v) { setAttr({ tdeeSize: v }); } })
253 ),
254 el(PanelColorSettings, {
255 title: __('Colors', 'blockenberg'),
256 initialOpen: false, colorSettings: [
257 { label: 'Accent', value: a.accentColor, onChange: function (v) { setAttr({ accentColor: v }); } },
258 { label: 'Card background', value: a.cardBg, onChange: function (v) { setAttr({ cardBg: v }); } },
259 { label: 'Result background',value: a.resultBg, onChange: function (v) { setAttr({ resultBg: v }); } },
260 { label: 'Result border', value: a.resultBorder, onChange: function (v) { setAttr({ resultBorder: v }); } },
261 { label: 'TDEE number', value: a.tdeeColor, onChange: function (v) { setAttr({ tdeeColor: v }); } },
262 { label: 'BMR label', value: a.bmrColor, onChange: function (v) { setAttr({ bmrColor: v }); } },
263 { label: 'Protein macro', value: a.proColor, onChange: function (v) { setAttr({ proColor: v }); } },
264 { label: 'Carbs macro', value: a.carbColor, onChange: function (v) { setAttr({ carbColor: v }); } },
265 { label: 'Fat macro', value: a.fatColor, onChange: function (v) { setAttr({ fatColor: v }); } },
266 { label: 'Title', value: a.titleColor, onChange: function (v) { setAttr({ titleColor: v }); } },
267 { label: 'Subtitle', value: a.subtitleColor, onChange: function (v) { setAttr({ subtitleColor: v }); } },
268 ],
269 }),
270 el(PanelBody, { title: __('Sizing', 'blockenberg'), initialOpen: false },
271 el(RangeControl, { label: 'Max width (px)', value: a.maxWidth, min:300, max:900, step:10, onChange: function (v) { setAttr({ maxWidth: v }); } }),
272 el(RangeControl, { label: 'Padding top', value: a.paddingTop, min:0, max:120, onChange: function (v) { setAttr({ paddingTop: v }); } }),
273 el(RangeControl, { label: 'Padding bottom', value: a.paddingBottom, min:0, max:120, onChange: function (v) { setAttr({ paddingBottom: v }); } }),
274 el(RangeControl, { label: 'Card radius (px)', value: a.cardRadius, min:0, max:32, onChange: function (v) { setAttr({ cardRadius: v }); } }),
275 el(RangeControl, { label: 'Input radius (px)',value: a.inputRadius, min:0, max:20, onChange: function (v) { setAttr({ inputRadius: v }); } }),
276 )
277 ),
278 el(CalcPreview, { attributes: a, setAttributes: setAttr })
279 );
280 },
281
282 save: function (props) {
283 var a = props.attributes;
284 var blockProps = wp.blockEditor.useBlockProps.save({ className: 'bkbg-calorie-calculator-block' });
285 return el('div', Object.assign({}, blockProps, {
286 className: (blockProps.className || '') + ' bkbg-cal-app',
287 'data-opts': JSON.stringify(a),
288 }));
289 },
290 });
291 }() );
292