| 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 TextControl = wp.components.TextControl; |
| 12 |
var Button = wp.components.Button; |
| 13 |
var SelectControl = wp.components.SelectControl; |
| 14 |
|
| 15 |
function getTypographyControl() { return (window.bkbgTypographyControl || function () { return null; }); } |
| 16 |
function getTypoCssVars() { return (window.bkbgTypoCssVars || function () { return {}; }); } |
| 17 |
function _tv(typo, prefix) { var fn = getTypoCssVars(); return fn(typo || {}, prefix); } |
| 18 |
var IP = function () { return window.bkbgIconPicker; }; |
| 19 |
|
| 20 |
/* Render a cell value: 'true'/'false' → icon, otherwise text */ |
| 21 |
function CellValue(val, attr) { |
| 22 |
if (val === 'true') { |
| 23 |
var checkType = attr.checkIconType || 'custom-char'; |
| 24 |
if (checkType !== 'custom-char') { |
| 25 |
return el('span', { className: 'bkbg-cm-check', style: { color: attr.checkColor, fontSize: 18 } }, |
| 26 |
IP().buildEditorIcon(checkType, attr.checkIcon, attr.checkIconDashicon, attr.checkIconImageUrl, attr.checkIconDashiconColor)); |
| 27 |
} |
| 28 |
return el('span', { style: { color: attr.checkColor, fontWeight: 700, fontSize: 18 }}, attr.checkIcon || '✓'); |
| 29 |
} |
| 30 |
if (val === 'false') { |
| 31 |
var crossType = attr.crossIconType || 'custom-char'; |
| 32 |
if (crossType !== 'custom-char') { |
| 33 |
return el('span', { className: 'bkbg-cm-cross', style: { color: attr.crossColor, fontSize: 18 } }, |
| 34 |
IP().buildEditorIcon(crossType, attr.crossIcon, attr.crossIconDashicon, attr.crossIconImageUrl, attr.crossIconDashiconColor)); |
| 35 |
} |
| 36 |
return el('span', { style: { color: attr.crossColor, fontWeight: 700, fontSize: 18 }}, attr.crossIcon || '✕'); |
| 37 |
} |
| 38 |
return el('span', { style: { fontWeight: 600 }}, val); |
| 39 |
} |
| 40 |
|
| 41 |
registerBlockType('blockenberg/comparison-matrix', { |
| 42 |
title: __('Comparison Matrix'), |
| 43 |
icon: 'grid-view', |
| 44 |
category: 'bkbg-marketing', |
| 45 |
|
| 46 |
edit: function (props) { |
| 47 |
var attr = props.attributes; |
| 48 |
var setAttr = props.setAttributes; |
| 49 |
var typoVars = Object.assign({}, _tv(attr.typoHeader, '--bkcm-header-'), _tv(attr.typoBody, '--bkcm-body-')); |
| 50 |
var bp = useBlockProps({ className: 'bkbg-cm-wrap', style: typoVars }); |
| 51 |
|
| 52 |
function updatePlan(idx, field, val) { |
| 53 |
var plans = attr.plans.map(function (p, i) { |
| 54 |
return i === idx ? Object.assign({}, p, { [field]: val }) : p; |
| 55 |
}); |
| 56 |
setAttr({ plans: plans }); |
| 57 |
} |
| 58 |
function addPlan() { |
| 59 |
var n = attr.plans.length; |
| 60 |
var newValues = attr.features.map(function (f) { |
| 61 |
var newF = f.values.slice(); |
| 62 |
newF.push('true'); |
| 63 |
return newF; |
| 64 |
}); |
| 65 |
setAttr({ |
| 66 |
plans: attr.plans.concat([{ name: 'Plan ' + (n + 1), subtitle: '', highlight: false, badge: '' }]), |
| 67 |
features: attr.features.map(function (f, i) { |
| 68 |
return Object.assign({}, f, { values: newValues[i] }); |
| 69 |
}) |
| 70 |
}); |
| 71 |
} |
| 72 |
function removePlan(idx) { |
| 73 |
if (attr.plans.length <= 2) return; |
| 74 |
setAttr({ |
| 75 |
plans: attr.plans.filter(function (_, i) { return i !== idx; }), |
| 76 |
features: attr.features.map(function (f) { |
| 77 |
return Object.assign({}, f, { values: f.values.filter(function (_, i) { return i !== idx; }) }); |
| 78 |
}) |
| 79 |
}); |
| 80 |
} |
| 81 |
function updateFeature(idx, field, val) { |
| 82 |
var features = attr.features.map(function (f, i) { |
| 83 |
return i === idx ? Object.assign({}, f, { [field]: val }) : f; |
| 84 |
}); |
| 85 |
setAttr({ features: features }); |
| 86 |
} |
| 87 |
function updateFeatureValue(fidx, pidx, val) { |
| 88 |
var features = attr.features.map(function (f, i) { |
| 89 |
if (i !== fidx) return f; |
| 90 |
var values = f.values.slice(); |
| 91 |
values[pidx] = val; |
| 92 |
return Object.assign({}, f, { values: values }); |
| 93 |
}); |
| 94 |
setAttr({ features: features }); |
| 95 |
} |
| 96 |
function addFeature() { |
| 97 |
setAttr({ features: attr.features.concat([{ |
| 98 |
label: 'New Feature', group: '', |
| 99 |
values: attr.plans.map(function () { return 'true'; }) |
| 100 |
}]) }); |
| 101 |
} |
| 102 |
function removeFeature(idx) { |
| 103 |
setAttr({ features: attr.features.filter(function (_, i) { return i !== idx; }) }); |
| 104 |
} |
| 105 |
|
| 106 |
var inspector = el(InspectorControls, {}, |
| 107 |
/* Plans */ |
| 108 |
el(PanelBody, { title: __('Plans / Columns'), initialOpen: true }, |
| 109 |
attr.plans.map(function (p, idx) { |
| 110 |
return el(PanelBody, { key: idx, title: p.name || 'Plan ' + (idx + 1), initialOpen: false }, |
| 111 |
el(TextControl, { label: __('Name'), value: p.name || '', |
| 112 |
onChange: function (v) { updatePlan(idx, 'name', v); }, |
| 113 |
__nextHasNoMarginBottom: true }), |
| 114 |
el(TextControl, { label: __('Subtitle'), value: p.subtitle || '', |
| 115 |
onChange: function (v) { updatePlan(idx, 'subtitle', v); }, |
| 116 |
__nextHasNoMarginBottom: true }), |
| 117 |
el(TextControl, { label: __('Badge (optional)'), value: p.badge || '', |
| 118 |
onChange: function (v) { updatePlan(idx, 'badge', v); }, |
| 119 |
__nextHasNoMarginBottom: true }), |
| 120 |
el(ToggleControl, { label: __('Highlight (featured)'), checked: !!p.highlight, |
| 121 |
onChange: function (v) { updatePlan(idx, 'highlight', v); }, |
| 122 |
__nextHasNoMarginBottom: true }), |
| 123 |
attr.plans.length > 2 && el(Button, { isDestructive: true, variant: 'secondary', |
| 124 |
onClick: function () { removePlan(idx); }, |
| 125 |
style: { marginTop: 8 } |
| 126 |
}, __('Remove Plan')) |
| 127 |
); |
| 128 |
}), |
| 129 |
el(Button, { variant: 'primary', onClick: addPlan, |
| 130 |
style: { marginTop: 8, width: '100%' } |
| 131 |
}, __('+ Add Plan')) |
| 132 |
), |
| 133 |
|
| 134 |
/* Features */ |
| 135 |
el(PanelBody, { title: __('Features / Rows'), initialOpen: false }, |
| 136 |
attr.features.map(function (f, fidx) { |
| 137 |
return el(PanelBody, { key: fidx, title: f.label || 'Feature ' + (fidx + 1), initialOpen: false }, |
| 138 |
el(TextControl, { label: __('Feature Label'), value: f.label || '', |
| 139 |
onChange: function (v) { updateFeature(fidx, 'label', v); }, |
| 140 |
__nextHasNoMarginBottom: true }), |
| 141 |
el(TextControl, { label: __('Group Header (optional)'), value: f.group || '', |
| 142 |
onChange: function (v) { updateFeature(fidx, 'group', v); }, |
| 143 |
__nextHasNoMarginBottom: true }), |
| 144 |
el('p', { style: { fontSize: 11, margin: '8px 0 4px', color: '#64748b' }}, |
| 145 |
__('Values per plan — use "true"/false" for check/cross, or any text:')), |
| 146 |
attr.plans.map(function (p, pidx) { |
| 147 |
return el(TextControl, { key: pidx, |
| 148 |
label: p.name || 'Plan ' + (pidx + 1), |
| 149 |
value: (f.values && f.values[pidx]) || 'true', |
| 150 |
onChange: function (v) { updateFeatureValue(fidx, pidx, v); }, |
| 151 |
__nextHasNoMarginBottom: true }); |
| 152 |
}), |
| 153 |
el(Button, { isDestructive: true, variant: 'secondary', |
| 154 |
onClick: function () { removeFeature(fidx); }, |
| 155 |
style: { marginTop: 8 } |
| 156 |
}, __('Remove Feature')) |
| 157 |
); |
| 158 |
}), |
| 159 |
el(Button, { variant: 'primary', onClick: addFeature, |
| 160 |
style: { marginTop: 8, width: '100%' } |
| 161 |
}, __('+ Add Feature')) |
| 162 |
), |
| 163 |
|
| 164 |
/* Appearance */ |
| 165 |
el(PanelBody, { title: __('Appearance'), initialOpen: false }, |
| 166 |
el(RangeControl, { label: __('Cell Padding (px)'), value: attr.cellPadding, |
| 167 |
min: 8, max: 32, |
| 168 |
onChange: function (v) { setAttr({ cellPadding: v }); }, |
| 169 |
__nextHasNoMarginBottom: true }), |
| 170 |
el(RangeControl, { label: __('Header Radius (px)'), value: attr.headerRadius, |
| 171 |
min: 0, max: 24, |
| 172 |
onChange: function (v) { setAttr({ headerRadius: v }); }, |
| 173 |
__nextHasNoMarginBottom: true }), |
| 174 |
el(RangeControl, { label: __('First Column Width (px)'), value: attr.firstColWidth, |
| 175 |
min: 100, max: 400, |
| 176 |
onChange: function (v) { setAttr({ firstColWidth: v }); }, |
| 177 |
__nextHasNoMarginBottom: true }), |
| 178 |
el(ToggleControl, { label: __('Sticky Header Row'), checked: attr.stickyHeader, |
| 179 |
onChange: function (v) { setAttr({ stickyHeader: v }); }, |
| 180 |
__nextHasNoMarginBottom: true }), |
| 181 |
el('p', { style: { fontSize: 11, fontWeight: 600, marginTop: 12 } }, __('Check Icon')), |
| 182 |
el(IP().IconPickerControl, IP().iconPickerProps(attr, setAttr, { charAttr: 'checkIcon', typeAttr: 'checkIconType', dashiconAttr: 'checkIconDashicon', imageUrlAttr: 'checkIconImageUrl', colorAttr: 'checkIconDashiconColor' })), |
| 183 |
el('p', { style: { fontSize: 11, fontWeight: 600, marginTop: 12 } }, __('Cross Icon')), |
| 184 |
el(IP().IconPickerControl, IP().iconPickerProps(attr, setAttr, { charAttr: 'crossIcon', typeAttr: 'crossIconType', dashiconAttr: 'crossIconDashicon', imageUrlAttr: 'crossIconImageUrl', colorAttr: 'crossIconDashiconColor' })) |
| 185 |
), |
| 186 |
|
| 187 |
|
| 188 |
el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false }, |
| 189 |
el(getTypographyControl(), { label: __('Header Typography', 'blockenberg'), value: attr.typoHeader, onChange: function (v) { setAttr({ typoHeader: v }); } }), |
| 190 |
el(getTypographyControl(), { label: __('Body Typography', 'blockenberg'), value: attr.typoBody, onChange: function (v) { setAttr({ typoBody: v }); } }) |
| 191 |
), |
| 192 |
el(PanelColorSettings, { |
| 193 |
title: __('Colors'), initialOpen: false, |
| 194 |
colorSettings: [ |
| 195 |
{ label: __('Header BG'), value: attr.headerBg, |
| 196 |
onChange: function (v) { setAttr({ headerBg: v || '#f8fafc' }); } }, |
| 197 |
{ label: __('Header Text'), value: attr.headerColor, |
| 198 |
onChange: function (v) { setAttr({ headerColor: v || '#0f172a' }); } }, |
| 199 |
{ label: __('Highlight BG'), value: attr.highlightBg, |
| 200 |
onChange: function (v) { setAttr({ highlightBg: v || '#6366f1' }); } }, |
| 201 |
{ label: __('Highlight Text'), value: attr.highlightColor, |
| 202 |
onChange: function (v) { setAttr({ highlightColor: v || '#ffffff' }); } }, |
| 203 |
{ label: __('Row Color'), value: attr.rowColor, |
| 204 |
onChange: function (v) { setAttr({ rowColor: v || '#334155' }); } }, |
| 205 |
{ label: __('Border'), value: attr.borderColor, |
| 206 |
onChange: function (v) { setAttr({ borderColor: v || '#e2e8f0' }); } }, |
| 207 |
{ label: __('Check'), value: attr.checkColor, |
| 208 |
onChange: function (v) { setAttr({ checkColor: v || '#10b981' }); } }, |
| 209 |
{ label: __('Cross'), value: attr.crossColor, |
| 210 |
onChange: function (v) { setAttr({ crossColor: v || '#f43f5e' }); } } |
| 211 |
] |
| 212 |
}) |
| 213 |
); |
| 214 |
|
| 215 |
/* Editor preview table */ |
| 216 |
var colStyle = function (p) { |
| 217 |
return { |
| 218 |
background: p.highlight ? attr.highlightBg : attr.headerBg, |
| 219 |
color: p.highlight ? attr.highlightColor : attr.headerColor, |
| 220 |
padding: attr.cellPadding + 'px', |
| 221 |
textAlign: 'center', |
| 222 |
borderRadius: attr.headerRadius + 'px ' + attr.headerRadius + 'px 0 0', |
| 223 |
border: '1px solid ' + attr.borderColor, borderBottom: 'none' |
| 224 |
}; |
| 225 |
}; |
| 226 |
|
| 227 |
return el(wp.element.Fragment, {}, inspector, |
| 228 |
el('div', bp, |
| 229 |
el('div', { style: { overflowX: 'auto' }}, |
| 230 |
el('table', { className: 'bkbg-cm-table', style: { |
| 231 |
width: '100%', borderCollapse: 'collapse' |
| 232 |
}}, |
| 233 |
el('thead', {}, |
| 234 |
el('tr', {}, |
| 235 |
el('th', { className: 'bkbg-cm-th bkbg-cm-th-feature', style: { |
| 236 |
width: attr.firstColWidth + 'px', |
| 237 |
padding: attr.cellPadding + 'px', |
| 238 |
border: '1px solid ' + attr.borderColor, |
| 239 |
background: attr.headerBg |
| 240 |
}}), |
| 241 |
attr.plans.map(function (p, i) { |
| 242 |
return el('th', { key: i, className: 'bkbg-cm-th' + (p.highlight ? ' bkbg-cm-highlight' : ''), style: colStyle(p) }, |
| 243 |
p.badge && el('div', { style: { |
| 244 |
fontSize: 10, fontWeight: 700, marginBottom: 4, |
| 245 |
background: attr.badgeBg || '#fef3c7', |
| 246 |
color: attr.badgeColor || '#92400e', |
| 247 |
padding: '2px 8px', borderRadius: 100, |
| 248 |
display: 'inline-block' |
| 249 |
}}, p.badge), |
| 250 |
el('div', {}, p.name), |
| 251 |
p.subtitle && el('div', { style: { fontSize: 11, opacity: 0.75, fontWeight: 400 }}, p.subtitle) |
| 252 |
); |
| 253 |
}) |
| 254 |
) |
| 255 |
), |
| 256 |
el('tbody', {}, |
| 257 |
attr.features.map(function (f, ridx) { |
| 258 |
return el('tr', { key: ridx, style: { |
| 259 |
background: ridx % 2 === 0 ? attr.rowBg || '#ffffff' : attr.rowAltBg || '#f8fafc' |
| 260 |
}}, |
| 261 |
el('td', { className: 'bkbg-cm-td bkbg-cm-td-feature', style: { |
| 262 |
padding: attr.cellPadding + 'px', |
| 263 |
border: '1px solid ' + attr.borderColor, |
| 264 |
color: attr.headerColor, |
| 265 |
width: attr.firstColWidth + 'px' |
| 266 |
}}, f.label), |
| 267 |
attr.plans.map(function (p, cidx) { |
| 268 |
var val = f.values && f.values[cidx] != null ? f.values[cidx] : ''; |
| 269 |
return el('td', { key: cidx, className: 'bkbg-cm-td' + (p.highlight ? ' bkbg-cm-hl-col' : ''), style: { |
| 270 |
padding: attr.cellPadding + 'px', |
| 271 |
border: '1px solid ' + attr.borderColor, |
| 272 |
color: attr.rowColor, |
| 273 |
background: p.highlight ? attr.highlightBg + '18' : 'transparent' |
| 274 |
}}, |
| 275 |
CellValue(val, attr) |
| 276 |
); |
| 277 |
}) |
| 278 |
); |
| 279 |
}) |
| 280 |
) |
| 281 |
) |
| 282 |
) |
| 283 |
) |
| 284 |
); |
| 285 |
}, |
| 286 |
|
| 287 |
save: function (props) { |
| 288 |
var attr = props.attributes; |
| 289 |
var sv = Object.assign({}, _tv(attr.typoHeader, '--bkcm-header-'), _tv(attr.typoBody, '--bkcm-body-')); |
| 290 |
var bp = useBlockProps.save({ style: sv }); |
| 291 |
return el('div', bp, |
| 292 |
el('div', { className: 'bkbg-cm-app', 'data-opts': JSON.stringify(attr) }) |
| 293 |
); |
| 294 |
} |
| 295 |
}); |
| 296 |
}() ); |
| 297 |
|