| 1 |
( function () { |
| 2 |
var el = wp.element.createElement; |
| 3 |
var Fragment = wp.element.Fragment; |
| 4 |
var __ = wp.i18n.__; |
| 5 |
var registerBlockType = wp.blocks.registerBlockType; |
| 6 |
var InspectorControls = wp.blockEditor.InspectorControls; |
| 7 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 8 |
var PanelColorSettings = wp.blockEditor.PanelColorSettings; |
| 9 |
var PanelBody = wp.components.PanelBody; |
| 10 |
var Button = wp.components.Button; |
| 11 |
var TextControl = wp.components.TextControl; |
| 12 |
var ToggleControl = wp.components.ToggleControl; |
| 13 |
var RangeControl = wp.components.RangeControl; |
| 14 |
var SelectControl = wp.components.SelectControl; |
| 15 |
var CheckboxControl = wp.components.CheckboxControl; |
| 16 |
|
| 17 |
var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); } |
| 18 |
var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); } |
| 19 |
|
| 20 |
// ── ES5 safe update helpers ────────────────────────────────── |
| 21 |
function updateGroup(groups, gIdx, field, val) { |
| 22 |
return groups.map(function (g, i) { |
| 23 |
if (i !== gIdx) return g; |
| 24 |
var u = {}; u[field] = val; |
| 25 |
return Object.assign({}, g, u); |
| 26 |
}); |
| 27 |
} |
| 28 |
|
| 29 |
function updateSpec(groups, gIdx, sIdx, field, val) { |
| 30 |
return groups.map(function (g, i) { |
| 31 |
if (i !== gIdx) return g; |
| 32 |
var newSpecs = g.specs.map(function (s, j) { |
| 33 |
if (j !== sIdx) return s; |
| 34 |
var u = {}; u[field] = val; |
| 35 |
return Object.assign({}, s, u); |
| 36 |
}); |
| 37 |
return Object.assign({}, g, { specs: newSpecs }); |
| 38 |
}); |
| 39 |
} |
| 40 |
|
| 41 |
function addSpec(groups, gIdx) { |
| 42 |
return groups.map(function (g, i) { |
| 43 |
if (i !== gIdx) return g; |
| 44 |
return Object.assign({}, g, { specs: g.specs.concat([{ key: 'Spec', value: 'Value', highlight: false }]) }); |
| 45 |
}); |
| 46 |
} |
| 47 |
|
| 48 |
function removeSpec(groups, gIdx, sIdx) { |
| 49 |
return groups.map(function (g, i) { |
| 50 |
if (i !== gIdx) return g; |
| 51 |
return Object.assign({}, g, { specs: g.specs.filter(function (_, j) { return j !== sIdx; }) }); |
| 52 |
}); |
| 53 |
} |
| 54 |
|
| 55 |
registerBlockType('blockenberg/product-spec', { |
| 56 |
title: __('Product Spec Sheet', 'blockenberg'), |
| 57 |
icon: 'editor-table', |
| 58 |
category: 'bkbg-marketing', |
| 59 |
description: __('Structured product specification sheet with grouped categories and rich styling.', 'blockenberg'), |
| 60 |
|
| 61 |
edit: function (props) { |
| 62 |
var a = props.attributes; |
| 63 |
var set = props.setAttributes; |
| 64 |
var TC = getTypoControl(); |
| 65 |
|
| 66 |
var layoutOptions = [ |
| 67 |
{ label: __('Table', 'blockenberg'), value: 'table' }, |
| 68 |
{ label: __('Cards', 'blockenberg'), value: 'cards' }, |
| 69 |
{ label: __('Compact (2 cols)', 'blockenberg'), value: 'compact' } |
| 70 |
]; |
| 71 |
var styleOptions = [ |
| 72 |
{ label: __('Striped rows', 'blockenberg'), value: 'striped' }, |
| 73 |
{ label: __('Bordered', 'blockenberg'), value: 'bordered' }, |
| 74 |
{ label: __('Minimal', 'blockenberg'), value: 'minimal' } |
| 75 |
]; |
| 76 |
|
| 77 |
var inspector = el(InspectorControls, {}, |
| 78 |
// Header |
| 79 |
el(PanelBody, { title: __('Product Header', 'blockenberg'), initialOpen: true }, |
| 80 |
el(ToggleControl, { |
| 81 |
label: __('Show product header', 'blockenberg'), checked: a.showHeader, __nextHasNoMarginBottom: true, |
| 82 |
onChange: function (v) { set({ showHeader: v }); } |
| 83 |
}), |
| 84 |
a.showHeader && el(TextControl, { |
| 85 |
label: __('Product name', 'blockenberg'), value: a.productName, __nextHasNoMarginBottom: true, |
| 86 |
onChange: function (v) { set({ productName: v }); } |
| 87 |
}), |
| 88 |
a.showHeader && el(TextControl, { |
| 89 |
label: __('Tagline', 'blockenberg'), value: a.productTagline, __nextHasNoMarginBottom: true, |
| 90 |
onChange: function (v) { set({ productTagline: v }); } |
| 91 |
}), |
| 92 |
), |
| 93 |
|
| 94 |
// Spec Groups |
| 95 |
el(PanelBody, { title: __('Spec Groups', 'blockenberg'), initialOpen: false }, |
| 96 |
a.groups.map(function (group, gIdx) { |
| 97 |
return el('div', { key: 'g-' + gIdx, style: { marginBottom: '16px', border: '1px solid #e2e8f0', borderRadius: '8px', overflow: 'hidden' } }, |
| 98 |
el('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', background: '#f8fafc' } }, |
| 99 |
el(TextControl, { |
| 100 |
label: '', |
| 101 |
value: group.title, __nextHasNoMarginBottom: true, |
| 102 |
style: { fontWeight: 700, margin: 0 }, |
| 103 |
onChange: function (v) { set({ groups: updateGroup(a.groups, gIdx, 'title', v) }); } |
| 104 |
}), |
| 105 |
el('div', { style: { display: 'flex', gap: '4px' } }, |
| 106 |
el(Button, { |
| 107 |
isSmall: true, icon: 'plus-alt2', |
| 108 |
onClick: function () { set({ groups: addSpec(a.groups, gIdx) }); } |
| 109 |
}), |
| 110 |
el(Button, { |
| 111 |
isSmall: true, isDestructive: true, icon: 'trash', |
| 112 |
disabled: a.groups.length <= 1, |
| 113 |
onClick: function () { set({ groups: a.groups.filter(function (_, i) { return i !== gIdx; }) }); } |
| 114 |
}) |
| 115 |
) |
| 116 |
), |
| 117 |
el('div', { style: { padding: '8px 12px', display: 'flex', flexDirection: 'column', gap: '6px' } }, |
| 118 |
group.specs.map(function (spec, sIdx) { |
| 119 |
return el('div', { key: 's-' + sIdx, style: { display: 'flex', gap: '6px', alignItems: 'center' } }, |
| 120 |
el(TextControl, { |
| 121 |
label: '', value: spec.key, __nextHasNoMarginBottom: true, |
| 122 |
placeholder: __('Key', 'blockenberg'), |
| 123 |
onChange: function (v) { set({ groups: updateSpec(a.groups, gIdx, sIdx, 'key', v) }); } |
| 124 |
}), |
| 125 |
el(TextControl, { |
| 126 |
label: '', value: spec.value, __nextHasNoMarginBottom: true, |
| 127 |
placeholder: __('Value', 'blockenberg'), |
| 128 |
onChange: function (v) { set({ groups: updateSpec(a.groups, gIdx, sIdx, 'value', v) }); } |
| 129 |
}), |
| 130 |
el(Button, { |
| 131 |
isSmall: true, isDestructive: true, icon: 'trash', |
| 132 |
disabled: group.specs.length <= 1, |
| 133 |
onClick: function () { set({ groups: removeSpec(a.groups, gIdx, sIdx) }); } |
| 134 |
}) |
| 135 |
); |
| 136 |
}) |
| 137 |
) |
| 138 |
); |
| 139 |
}), |
| 140 |
el(Button, { |
| 141 |
variant: 'secondary', isSmall: true, icon: 'plus-alt2', |
| 142 |
onClick: function () { |
| 143 |
set({ groups: a.groups.concat([{ title: 'New Group', specs: [{ key: 'Spec', value: 'Value', highlight: false }] }]) }); |
| 144 |
} |
| 145 |
}, __('Add Group', 'blockenberg')) |
| 146 |
), |
| 147 |
|
| 148 |
// Display |
| 149 |
el(PanelBody, { title: __('Display', 'blockenberg'), initialOpen: false }, |
| 150 |
el(SelectControl, { |
| 151 |
label: __('Layout', 'blockenberg'), value: a.layout, options: layoutOptions, __nextHasNoMarginBottom: true, |
| 152 |
onChange: function (v) { set({ layout: v }); } |
| 153 |
}), |
| 154 |
el(SelectControl, { |
| 155 |
label: __('Style', 'blockenberg'), value: a.style, options: styleOptions, __nextHasNoMarginBottom: true, |
| 156 |
onChange: function (v) { set({ style: v }); } |
| 157 |
}), |
| 158 |
el(ToggleControl, { |
| 159 |
label: __('Show group titles', 'blockenberg'), checked: a.showGroupTitles, __nextHasNoMarginBottom: true, |
| 160 |
onChange: function (v) { set({ showGroupTitles: v }); } |
| 161 |
}), |
| 162 |
el(ToggleControl, { |
| 163 |
label: __('Highlight starred rows', 'blockenberg'), checked: a.showHighlight, __nextHasNoMarginBottom: true, |
| 164 |
onChange: function (v) { set({ showHighlight: v }); } |
| 165 |
}), |
| 166 |
el(RangeControl, { |
| 167 |
label: __('Border radius', 'blockenberg'), value: a.borderRadius, min: 0, max: 24, __nextHasNoMarginBottom: true, |
| 168 |
onChange: function (v) { set({ borderRadius: v }); } |
| 169 |
}) |
| 170 |
), |
| 171 |
|
| 172 |
// Typography |
| 173 |
el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false }, |
| 174 |
TC && el(TC, { label: __('Product Header', 'blockenberg'), typo: a.headerTypo || {}, onChange: function (v) { set({ headerTypo: v }); } }), |
| 175 |
TC && el(TC, { label: __('Group Title', 'blockenberg'), typo: a.groupTitleTypo || {}, onChange: function (v) { set({ groupTitleTypo: v }); } }), |
| 176 |
TC && el(TC, { label: __('Row Text', 'blockenberg'), typo: a.rowTypo || {}, onChange: function (v) { set({ rowTypo: v }); } }) |
| 177 |
), |
| 178 |
|
| 179 |
// Colors |
| 180 |
el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false }, |
| 181 |
el(PanelColorSettings, { |
| 182 |
title: __('Table Colors', 'blockenberg'), initialOpen: false, |
| 183 |
colorSettings: [ |
| 184 |
{ value: a.bgColor, label: __('Background', 'blockenberg'), onChange: function (c) { set({ bgColor: c }); } }, |
| 185 |
{ value: a.borderColor, label: __('Borders', 'blockenberg'), onChange: function (c) { set({ borderColor: c }); } }, |
| 186 |
{ value: a.keyColor, label: __('Key text', 'blockenberg'), onChange: function (c) { set({ keyColor: c }); } }, |
| 187 |
{ value: a.valueColor, label: __('Value text', 'blockenberg'), onChange: function (c) { set({ valueColor: c }); } }, |
| 188 |
{ value: a.stripeBg, label: __('Stripe row bg', 'blockenberg'), onChange: function (c) { set({ stripeBg: c }); } }, |
| 189 |
{ value: a.accentColor, label: __('Accent', 'blockenberg'), onChange: function (c) { set({ accentColor: c }); } } |
| 190 |
] |
| 191 |
}), |
| 192 |
el(PanelColorSettings, { |
| 193 |
title: __('Header & Group Colors', 'blockenberg'), initialOpen: false, |
| 194 |
colorSettings: [ |
| 195 |
{ value: a.headerBg, label: __('Header background', 'blockenberg'), onChange: function (c) { set({ headerBg: c }); } }, |
| 196 |
{ value: a.headerColor, label: __('Header text', 'blockenberg'), onChange: function (c) { set({ headerColor: c }); } }, |
| 197 |
{ value: a.groupTitleBg, label: __('Group title bg', 'blockenberg'), onChange: function (c) { set({ groupTitleBg: c }); } }, |
| 198 |
{ value: a.groupTitleColor, label: __('Group title text', 'blockenberg'), onChange: function (c) { set({ groupTitleColor: c }); } } |
| 199 |
] |
| 200 |
}), |
| 201 |
el(PanelColorSettings, { |
| 202 |
title: __('Highlight Colors', 'blockenberg'), initialOpen: false, |
| 203 |
colorSettings: [ |
| 204 |
{ value: a.highlightBg, label: __('Highlight background', 'blockenberg'), onChange: function (c) { set({ highlightBg: c }); } }, |
| 205 |
{ value: a.highlightColor, label: __('Highlight text', 'blockenberg'), onChange: function (c) { set({ highlightColor: c }); } } |
| 206 |
] |
| 207 |
}) |
| 208 |
) |
| 209 |
); |
| 210 |
|
| 211 |
// ── Preview ────────────────────────────────────────────── |
| 212 |
var isCards = a.layout === 'cards'; |
| 213 |
var isCompact = a.layout === 'compact'; |
| 214 |
|
| 215 |
var tablePreview = el('div', { |
| 216 |
style: { |
| 217 |
border: '1px solid ' + a.borderColor, |
| 218 |
borderRadius: a.borderRadius + 'px', |
| 219 |
overflow: 'hidden' |
| 220 |
} |
| 221 |
}, |
| 222 |
a.showHeader && el('div', { |
| 223 |
style: { |
| 224 |
background: a.headerBg, |
| 225 |
padding: '20px 24px', |
| 226 |
color: a.headerColor |
| 227 |
} |
| 228 |
}, |
| 229 |
el('div', { className: 'bkbg-ps-product-name', style: { color: a.headerColor } }, a.productName), |
| 230 |
a.productTagline && el('div', { style: { fontSize: '14px', opacity: 0.75, marginTop: '4px' } }, a.productTagline) |
| 231 |
), |
| 232 |
|
| 233 |
isCards |
| 234 |
? el('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', padding: '16px' } }, |
| 235 |
a.groups.map(function (group, gIdx) { |
| 236 |
return el('div', { key: 'gc-' + gIdx, style: { border: '1px solid ' + a.borderColor, borderRadius: '8px', overflow: 'hidden' } }, |
| 237 |
a.showGroupTitles && el('div', { |
| 238 |
className: 'bkbg-ps-group-title', |
| 239 |
style: { background: a.groupTitleBg, padding: '8px 14px', color: a.groupTitleColor, borderBottom: '1px solid ' + a.borderColor } |
| 240 |
}, group.title), |
| 241 |
el('div', { style: { padding: '0' } }, |
| 242 |
group.specs.map(function (spec, sIdx) { |
| 243 |
var isHighlighted = a.showHighlight && spec.highlight; |
| 244 |
var isStripe = a.style === 'striped' && sIdx % 2 === 1; |
| 245 |
return el('div', { |
| 246 |
key: 'sc-' + sIdx, |
| 247 |
style: { |
| 248 |
display: 'flex', justifyContent: 'space-between', alignItems: 'center', |
| 249 |
padding: '8px 14px', |
| 250 |
background: isHighlighted ? a.highlightBg : isStripe ? a.stripeBg : a.bgColor, |
| 251 |
borderBottom: '1px solid ' + a.borderColor |
| 252 |
} |
| 253 |
}, |
| 254 |
el('span', { className: 'bkbg-ps-key', style: { color: isHighlighted ? a.highlightColor : a.keyColor } }, spec.key), |
| 255 |
el('span', { className: 'bkbg-ps-value', style: { color: isHighlighted ? a.highlightColor : a.valueColor, textAlign: 'right', maxWidth: '55%' } }, spec.value) |
| 256 |
); |
| 257 |
}) |
| 258 |
) |
| 259 |
); |
| 260 |
}) |
| 261 |
) |
| 262 |
: el('table', { style: { width: '100%', borderCollapse: 'collapse' } }, |
| 263 |
a.groups.map(function (group, gIdx) { |
| 264 |
return el(Fragment, { key: 'gf-' + gIdx }, |
| 265 |
a.showGroupTitles && el('tr', {}, |
| 266 |
el('td', { |
| 267 |
className: 'bkbg-ps-group-title', |
| 268 |
colSpan: isCompact ? 4 : 2, |
| 269 |
style: { |
| 270 |
background: a.groupTitleBg, padding: '8px 16px', |
| 271 |
color: a.groupTitleColor, |
| 272 |
borderBottom: '1px solid ' + a.borderColor |
| 273 |
} |
| 274 |
}, group.title) |
| 275 |
), |
| 276 |
group.specs.map(function (spec, sIdx) { |
| 277 |
var isHighlighted = a.showHighlight && spec.highlight; |
| 278 |
var isStripe = a.style === 'striped' && sIdx % 2 === 1; |
| 279 |
return el('tr', { key: 'sr-' + sIdx }, |
| 280 |
el('td', { |
| 281 |
className: 'bkbg-ps-key', |
| 282 |
style: { |
| 283 |
padding: '10px 16px', |
| 284 |
color: isHighlighted ? a.highlightColor : a.keyColor, |
| 285 |
background: isHighlighted ? a.highlightBg : isStripe ? a.stripeBg : a.bgColor, |
| 286 |
borderBottom: '1px solid ' + a.borderColor |
| 287 |
} |
| 288 |
}, spec.key), |
| 289 |
el('td', { |
| 290 |
className: 'bkbg-ps-value', |
| 291 |
style: { |
| 292 |
padding: '10px 16px', |
| 293 |
color: isHighlighted ? a.highlightColor : a.valueColor, |
| 294 |
background: isHighlighted ? a.highlightBg : isStripe ? a.stripeBg : a.bgColor, |
| 295 |
borderBottom: '1px solid ' + a.borderColor |
| 296 |
} |
| 297 |
}, spec.value) |
| 298 |
); |
| 299 |
}) |
| 300 |
); |
| 301 |
}) |
| 302 |
) |
| 303 |
); |
| 304 |
|
| 305 |
var blockProps = useBlockProps((function () { |
| 306 |
var fn = getTypoCssVars(); |
| 307 |
var s = {}; |
| 308 |
if (fn) { |
| 309 |
Object.assign(s, fn(a.headerTypo || {}, '--bkbg-pspec-hd-')); |
| 310 |
Object.assign(s, fn(a.groupTitleTypo || {}, '--bkbg-pspec-gt-')); |
| 311 |
Object.assign(s, fn(a.rowTypo || {}, '--bkbg-pspec-rw-')); |
| 312 |
} |
| 313 |
return { className: 'bkbg-editor-wrap', 'data-block-label': 'Product Spec Sheet', style: s }; |
| 314 |
})()); |
| 315 |
|
| 316 |
return el('div', blockProps, inspector, tablePreview); |
| 317 |
}, |
| 318 |
|
| 319 |
save: function (props) { |
| 320 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 321 |
return el('div', useBlockProps.save(), |
| 322 |
el('div', { |
| 323 |
className: 'bkbg-product-spec-app', |
| 324 |
'data-opts': JSON.stringify(props.attributes) |
| 325 |
}) |
| 326 |
); |
| 327 |
} |
| 328 |
}); |
| 329 |
}() ); |
| 330 |
|