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 / call-to-action / index.js

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

679 lines 38.5 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 Fragment = wp.element.Fragment;
4 var useState = wp.element.useState;
5 var __ = wp.i18n.__;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var useBlockProps = wp.blockEditor.useBlockProps;
9 var RichText = wp.blockEditor.RichText;
10 var PanelBody = wp.components.PanelBody;
11 var ColorPicker = wp.components.ColorPicker;
12 var Popover = wp.components.Popover;
13 var Button = wp.components.Button;
14 var ToggleControl = wp.components.ToggleControl;
15 var RangeControl = wp.components.RangeControl;
16 var SelectControl = wp.components.SelectControl;
17 var TextControl = wp.components.TextControl;
18
19 // ── Typography helpers ──────────────────────────────────────────────────
20 function getTypographyControl() {
21 return (window.bkbgTypographyControl || function () { return null; });
22 }
23
24 function _tv(typo, prefix) {
25 if (!typo) return {};
26 var s = {};
27 if (typo.family) s[prefix + 'font-family'] = "'" + typo.family + "', sans-serif";
28 if (typo.weight) s[prefix + 'font-weight'] = typo.weight;
29 if (typo.transform) s[prefix + 'text-transform'] = typo.transform;
30 if (typo.style) s[prefix + 'font-style'] = typo.style;
31 if (typo.decoration) s[prefix + 'text-decoration'] = typo.decoration;
32 var su = typo.sizeUnit || 'px';
33 if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) s[prefix + 'font-size-d'] = typo.sizeDesktop + su;
34 if (typo.sizeTablet !== '' && typo.sizeTablet != null) s[prefix + 'font-size-t'] = typo.sizeTablet + su;
35 if (typo.sizeMobile !== '' && typo.sizeMobile != null) s[prefix + 'font-size-m'] = typo.sizeMobile + su;
36 var lhu = typo.lineHeightUnit || '';
37 if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) s[prefix + 'line-height-d'] = typo.lineHeightDesktop + lhu;
38 if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) s[prefix + 'line-height-t'] = typo.lineHeightTablet + lhu;
39 if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) s[prefix + 'line-height-m'] = typo.lineHeightMobile + lhu;
40 var lsu = typo.letterSpacingUnit || 'px';
41 if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) s[prefix + 'letter-spacing-d'] = typo.letterSpacingDesktop + lsu;
42 if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) s[prefix + 'letter-spacing-t'] = typo.letterSpacingTablet + lsu;
43 if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) s[prefix + 'letter-spacing-m'] = typo.letterSpacingMobile + lsu;
44 var wsu = typo.wordSpacingUnit || 'px';
45 if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) s[prefix + 'word-spacing-d'] = typo.wordSpacingDesktop + wsu;
46 if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) s[prefix + 'word-spacing-t'] = typo.wordSpacingTablet + wsu;
47 if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) s[prefix + 'word-spacing-m'] = typo.wordSpacingMobile + wsu;
48 return s;
49 }
50
51 // ── Helper: compute background CSS value ────────────────────────────────────
52 function computeBg(a) {
53 if (a.bgType === 'none') return 'none';
54 if (a.bgType === 'color') return a.bgColor;
55 if (a.bgType === 'gradient') {
56 return 'linear-gradient(' + a.bgGradientAngle + 'deg, ' + a.bgGradientFrom + ', ' + a.bgGradientTo + ')';
57 }
58 if (a.bgType === 'image' && a.bgImageUrl) {
59 return 'url(' + a.bgImageUrl + ')';
60 }
61 return 'none';
62 }
63
64 // ── Helper: wrap CSS vars + inline styles ───────────────────────────────────
65 function buildWrapStyle(a) {
66 var style = {
67 '--bkbg-cta-headline-color': a.headlineColor,
68 '--bkbg-cta-headline-spacing': a.headlineSpacing + 'px',
69 '--bkbg-cta-sub-color': a.subColor,
70 '--bkbg-cta-sub-spacing': a.subSpacing + 'px',
71 '--bkbg-cta-padding': a.paddingTop + 'px ' + a.paddingRight + 'px ' + a.paddingBottom + 'px ' + a.paddingLeft + 'px',
72 '--bkbg-cta-radius': a.borderRadius + 'px',
73 '--bkbg-cta-max-width': a.contentMaxWidth + 'px',
74 '--bkbg-cta-gap': a.gap + 'px',
75 '--bkbg-cta-text-align': a.textAlign,
76 '--bkbg-cta-overlay-color': a.bgOverlayColor,
77 '--bkbg-cta-btn1-bg': a.btn1Bg,
78 '--bkbg-cta-btn1-color': a.btn1Color,
79 '--bkbg-cta-btn1-bg-hover': a.btn1BgHover,
80 '--bkbg-cta-btn1-color-hover': a.btn1ColorHover,
81 '--bkbg-cta-btn1-brd-color': a.btn1BorderColor,
82 '--bkbg-cta-btn1-brd-w': a.btn1BorderWidth + 'px',
83 '--bkbg-cta-btn1-size': a.btn1Size + 'px',
84 '--bkbg-cta-btn1-weight': a.btn1Weight,
85 '--bkbg-cta-btn1-radius': a.btn1Radius + 'px',
86 '--bkbg-cta-btn1-padding': a.btn1PaddingV + 'px ' + a.btn1PaddingH + 'px',
87 '--bkbg-cta-btn2-bg': a.btn2Bg,
88 '--bkbg-cta-btn2-color': a.btn2Color,
89 '--bkbg-cta-btn2-bg-hover': a.btn2BgHover,
90 '--bkbg-cta-btn2-color-hover': a.btn2ColorHover,
91 '--bkbg-cta-btn2-brd-color': a.btn2BorderColor,
92 '--bkbg-cta-btn2-brd-w': a.btn2BorderWidth + 'px',
93 '--bkbg-cta-btn2-size': a.btn2Size + 'px',
94 '--bkbg-cta-btn2-weight': a.btn2Weight,
95 '--bkbg-cta-btn2-radius': a.btn2Radius + 'px',
96 '--bkbg-cta-btn2-padding': a.btn2PaddingV + 'px ' + a.btn2PaddingH + 'px'
97 };
98
99 var bg = computeBg(a);
100
101 if (a.bgType === 'image' && a.bgImageUrl) {
102 style['backgroundImage'] = bg;
103 style['backgroundSize'] = a.bgImageSize;
104 style['backgroundPosition'] = a.bgImagePosition;
105 style['backgroundRepeat'] = 'no-repeat';
106 } else if (a.bgType !== 'none') {
107 style['background'] = bg;
108 }
109
110 Object.assign(style, _tv(a.typoHeadline, '--bkbg-cta-h-'), _tv(a.typoSub, '--bkbg-cta-s-'));
111 return style;
112 }
113
114 registerBlockType('blockenberg/call-to-action', {
115 title: __('Call to Action', 'blockenberg'),
116 icon: 'megaphone',
117 category: 'bkbg-marketing',
118 description: __('Headline + sub-headline + 1–2 buttons with optional background.', 'blockenberg'),
119
120 edit: function (props) {
121 var a = props.attributes;
122 var setAttributes = props.setAttributes;
123
124 // ── Color picker state (one open at a time) ────────────────────
125 var openColorKeyState = useState(null);
126 var openColorKey = openColorKeyState[0];
127 var setOpenColorKey = openColorKeyState[1];
128
129 // ── Color control helper with alpha support ────────────────────────
130 function renderColorControl(key, label, value, onChange) {
131 var isOpen = openColorKey === key;
132 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
133 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
134 el('div', { style: { position: 'relative', flexShrink: 0 } },
135 el('button', {
136 type: 'button',
137 title: value || 'none',
138 onClick: function () { setOpenColorKey(isOpen ? null : key); },
139 style: {
140 width: '28px',
141 height: '28px',
142 borderRadius: '4px',
143 border: isOpen ? '2px solid #007cba' : '2px solid #ddd',
144 cursor: 'pointer',
145 padding: 0,
146 display: 'block',
147 background: value || '#ffffff',
148 flexShrink: 0
149 }
150 }),
151 isOpen && el(Popover, {
152 position: 'bottom left',
153 onClose: function () { setOpenColorKey(null); }
154 },
155 el('div', {
156 style: { padding: '8px' },
157 onMouseDown: function (e) { e.stopPropagation(); }
158 },
159 el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' } },
160 el('strong', { style: { fontSize: '12px' } }, label),
161 el(Button, { icon: 'no-alt', isSmall: true, onClick: function () { setOpenColorKey(null); } })
162 ),
163 el(ColorPicker, {
164 color: value,
165 enableAlpha: true,
166 onChange: function (c) { onChange(c); }
167 })
168 )
169 )
170 )
171 );
172 }
173
174 // ── Open media library for background image ────────────────────────
175 function openBgMediaLibrary() {
176 var frame = wp.media({
177 title: __('Select Background Image', 'blockenberg'),
178 button: { text: __('Use as Background', 'blockenberg') },
179 multiple: false,
180 library: { type: 'image' }
181 });
182 frame.on('select', function () {
183 var attachment = frame.state().get('selection').first().toJSON();
184 setAttributes({ bgImageUrl: attachment.url });
185 });
186 frame.open();
187 }
188
189 // ── Option lists ───────────────────────────────────────────────────
190 var titleTagOptions = [
191 { label: 'H1', value: 'h1' },
192 { label: 'H2', value: 'h2' },
193 { label: 'H3', value: 'h3' },
194 { label: 'H4', value: 'h4' },
195 { label: 'P', value: 'p' }
196 ];
197
198 var textAlignOptions = [
199 { label: __('Left', 'blockenberg'), value: 'left' },
200 { label: __('Center', 'blockenberg'), value: 'center' },
201 { label: __('Right', 'blockenberg'), value: 'right' }
202 ];
203
204 var btnLayoutOptions = [
205 { label: __('Row (side by side)', 'blockenberg'), value: 'row' },
206 { label: __('Column (stacked)', 'blockenberg'), value: 'column' }
207 ];
208
209 var bgTypeOptions = [
210 { label: __('None', 'blockenberg'), value: 'none' },
211 { label: __('Solid Color', 'blockenberg'), value: 'color' },
212 { label: __('Gradient', 'blockenberg'), value: 'gradient' },
213 { label: __('Image', 'blockenberg'), value: 'image' }
214 ];
215
216 var bgImageSizeOptions = [
217 { label: __('Cover', 'blockenberg'), value: 'cover' },
218 { label: __('Contain', 'blockenberg'), value: 'contain' },
219 { label: __('Auto', 'blockenberg'), value: 'auto' }
220 ];
221
222 var btnStyleOptions = [
223 { label: __('Solid Button', 'blockenberg'), value: 'solid' },
224 { label: __('Outline Button', 'blockenberg'), value: 'outline' },
225 { label: __('Ghost / Subtle', 'blockenberg'), value: 'ghost' },
226 { label: __('Plain Link', 'blockenberg'), value: 'text' }
227 ];
228
229 var targetOptions = [
230 { label: __('Same Tab', 'blockenberg'), value: '_self' },
231 { label: __('New Tab', 'blockenberg'), value: '_blank' }
232 ];
233
234 // ── Inspector Controls ─────────────────────────────────────────────
235 var inspector = el(InspectorControls, {},
236
237 // ── Layout ────────────────────────────────────────────────────
238 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
239 el(SelectControl, {
240 label: __('Text Alignment', 'blockenberg'),
241 value: a.textAlign,
242 options: textAlignOptions,
243 onChange: function (v) { setAttributes({ textAlign: v }); }
244 }),
245 el(SelectControl, {
246 label: __('Button Layout', 'blockenberg'),
247 value: a.btnLayout,
248 options: btnLayoutOptions,
249 onChange: function (v) { setAttributes({ btnLayout: v }); }
250 }),
251 el('hr', {}),
252 el('p', { style: { margin: '0 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __('Padding', 'blockenberg')),
253 el('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: '16px' } },
254 el(RangeControl, { label: __('Top', 'blockenberg'), value: a.paddingTop, min: 0, max: 160, onChange: function (v) { setAttributes({ paddingTop: v }); } }),
255 el(RangeControl, { label: __('Right', 'blockenberg'), value: a.paddingRight, min: 0, max: 160, onChange: function (v) { setAttributes({ paddingRight: v }); } }),
256 el(RangeControl, { label: __('Bottom', 'blockenberg'), value: a.paddingBottom, min: 0, max: 160, onChange: function (v) { setAttributes({ paddingBottom: v }); } }),
257 el(RangeControl, { label: __('Left', 'blockenberg'), value: a.paddingLeft, min: 0, max: 160, onChange: function (v) { setAttributes({ paddingLeft: v }); } })
258 ),
259 el(RangeControl, {
260 label: __('Border Radius', 'blockenberg'),
261 value: a.borderRadius,
262 min: 0,
263 max: 60,
264 onChange: function (v) { setAttributes({ borderRadius: v }); }
265 }),
266 el(RangeControl, {
267 label: __('Content Max Width (px)', 'blockenberg'),
268 value: a.contentMaxWidth,
269 min: 320,
270 max: 1400,
271 step: 10,
272 onChange: function (v) { setAttributes({ contentMaxWidth: v }); }
273 }),
274 el(RangeControl, {
275 label: __('Gap between elements', 'blockenberg'),
276 value: a.gap,
277 min: 0,
278 max: 60,
279 onChange: function (v) { setAttributes({ gap: v }); }
280 })
281 ),
282
283 // ── Background ────────────────────────────────────────────────
284 el(PanelBody, { title: __('Background', 'blockenberg'), initialOpen: false },
285 el(SelectControl, {
286 label: __('Background Type', 'blockenberg'),
287 value: a.bgType,
288 options: bgTypeOptions,
289 onChange: function (v) { setAttributes({ bgType: v }); }
290 }),
291 a.bgType === 'color' && el('div', { style: { padding: '4px 0 8px' } },
292 renderColorControl('bgColor', __('Color', 'blockenberg'), a.bgColor, function (c) { setAttributes({ bgColor: c }); })
293 ),
294 a.bgType === 'gradient' && el(Fragment, {},
295 el('div', { style: { padding: '4px 0 8px' } },
296 renderColorControl('bgGradientFrom', __('From', 'blockenberg'), a.bgGradientFrom, function (c) { setAttributes({ bgGradientFrom: c }); }),
297 renderColorControl('bgGradientTo', __('To', 'blockenberg'), a.bgGradientTo, function (c) { setAttributes({ bgGradientTo: c }); })
298 ),
299 el(RangeControl, {
300 label: __('Angle (deg)', 'blockenberg'),
301 value: a.bgGradientAngle,
302 min: 0,
303 max: 360,
304 onChange: function (v) { setAttributes({ bgGradientAngle: v }); }
305 })
306 ),
307 a.bgType === 'image' && el(Fragment, {},
308 a.bgImageUrl && el('div', { style: { marginBottom: '8px' } },
309 el('img', { src: a.bgImageUrl, alt: '', style: { width: '100%', height: '80px', objectFit: 'cover', borderRadius: '6px', display: 'block' } })
310 ),
311 el('div', { style: { display: 'flex', gap: '8px', marginBottom: '12px', flexWrap: 'wrap' } },
312 el(Button, {
313 variant: 'secondary',
314 isSmall: true,
315 onClick: openBgMediaLibrary
316 }, a.bgImageUrl ? __('Replace Image', 'blockenberg') : __('Select Image', 'blockenberg')),
317 a.bgImageUrl && el(Button, {
318 isSmall: true,
319 isDestructive: true,
320 onClick: function () { setAttributes({ bgImageUrl: '' }); }
321 }, __('Remove', 'blockenberg'))
322 ),
323 el(SelectControl, {
324 label: __('Image Size', 'blockenberg'),
325 value: a.bgImageSize,
326 options: bgImageSizeOptions,
327 onChange: function (v) { setAttributes({ bgImageSize: v }); }
328 }),
329 el(TextControl, {
330 label: __('Image Position', 'blockenberg'),
331 value: a.bgImagePosition,
332 help: __('e.g. center center, top left, 50% 30%', 'blockenberg'),
333 onChange: function (v) { setAttributes({ bgImagePosition: v }); }
334 }),
335 el('hr', {}),
336 el(ToggleControl, {
337 label: __('Color Overlay', 'blockenberg'),
338 checked: a.bgOverlay,
339 __nextHasNoMarginBottom: true,
340 onChange: function (v) { setAttributes({ bgOverlay: v }); }
341 }),
342 a.bgOverlay && el('div', { style: { padding: '4px 0 8px' } },
343 renderColorControl('bgOverlayColor', __('Overlay', 'blockenberg'), a.bgOverlayColor, function (c) { setAttributes({ bgOverlayColor: c }); })
344 )
345 )
346 ),
347
348 // ── Typography ────────────────────────────────────────────────
349 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
350 el(SelectControl, {
351 label: __('Headline Tag', 'blockenberg'),
352 value: a.titleTag,
353 options: titleTagOptions,
354 onChange: function (v) { setAttributes({ titleTag: v }); }
355 }),
356 el('p', { style: { margin: '12px 0 4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __('Headline', 'blockenberg')),
357 el(getTypographyControl(), {
358 label: __('Headline Typography', 'blockenberg'),
359 value: a.typoHeadline,
360 onChange: function (v) { setAttributes({ typoHeadline: v }); }
361 }),
362 el(RangeControl, {
363 label: __('Spacing Below', 'blockenberg'),
364 value: a.headlineSpacing,
365 min: 0,
366 max: 60,
367 onChange: function (v) { setAttributes({ headlineSpacing: v }); }
368 }),
369 el('hr', {}),
370 el('p', { style: { margin: '8px 0 4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __('Sub-headline', 'blockenberg')),
371 el(getTypographyControl(), {
372 label: __('Sub-headline Typography', 'blockenberg'),
373 value: a.typoSub,
374 onChange: function (v) { setAttributes({ typoSub: v }); }
375 }),
376 el(RangeControl, {
377 label: __('Spacing Below', 'blockenberg'),
378 value: a.subSpacing,
379 min: 0,
380 max: 80,
381 onChange: function (v) { setAttributes({ subSpacing: v }); }
382 }),
383 el('hr', {}),
384 el('p', { style: { margin: '8px 0 4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __('Button 1', 'blockenberg')),
385 el(RangeControl, {
386 label: __('Font Size', 'blockenberg'),
387 value: a.btn1Size,
388 min: 10,
389 max: 28,
390 onChange: function (v) { setAttributes({ btn1Size: v }); }
391 }),
392 el(RangeControl, {
393 label: __('Font Weight', 'blockenberg'),
394 value: a.btn1Weight,
395 min: 100,
396 max: 900,
397 step: 100,
398 onChange: function (v) { setAttributes({ btn1Weight: v }); }
399 }),
400 el('hr', {}),
401 el('p', { style: { margin: '8px 0 4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __('Button 2', 'blockenberg')),
402 el(RangeControl, {
403 label: __('Font Size', 'blockenberg'),
404 value: a.btn2Size,
405 min: 10,
406 max: 28,
407 onChange: function (v) { setAttributes({ btn2Size: v }); }
408 }),
409 el(RangeControl, {
410 label: __('Font Weight', 'blockenberg'),
411 value: a.btn2Weight,
412 min: 100,
413 max: 900,
414 step: 100,
415 onChange: function (v) { setAttributes({ btn2Weight: v }); }
416 })
417 ),
418
419 // ── Button 1 ──────────────────────────────────────────────────
420 el(PanelBody, { title: __('Button 1 (Primary)', 'blockenberg'), initialOpen: false },
421 el(TextControl, {
422 label: __('Button Text', 'blockenberg'),
423 value: a.btn1Text,
424 onChange: function (v) { setAttributes({ btn1Text: v }); }
425 }),
426 el(TextControl, {
427 label: __('URL', 'blockenberg'),
428 value: a.btn1Url,
429 type: 'url',
430 placeholder: 'https://',
431 onChange: function (v) { setAttributes({ btn1Url: v }); }
432 }),
433 el(SelectControl, {
434 label: __('Open In', 'blockenberg'),
435 value: a.btn1Target,
436 options: targetOptions,
437 onChange: function (v) { setAttributes({ btn1Target: v }); }
438 }),
439 el('hr', {}),
440 el(SelectControl, {
441 label: __('Button Style', 'blockenberg'),
442 value: a.btn1Style,
443 options: btnStyleOptions,
444 onChange: function (v) { setAttributes({ btn1Style: v }); }
445 }),
446 el(RangeControl, {
447 label: __('Border Radius', 'blockenberg'),
448 value: a.btn1Radius,
449 min: 0,
450 max: 50,
451 onChange: function (v) { setAttributes({ btn1Radius: v }); }
452 }),
453 el(RangeControl, {
454 label: __('Padding Vertical', 'blockenberg'),
455 value: a.btn1PaddingV,
456 min: 4,
457 max: 36,
458 onChange: function (v) { setAttributes({ btn1PaddingV: v }); }
459 }),
460 el(RangeControl, {
461 label: __('Padding Horizontal', 'blockenberg'),
462 value: a.btn1PaddingH,
463 min: 8,
464 max: 80,
465 onChange: function (v) { setAttributes({ btn1PaddingH: v }); }
466 }),
467 (a.btn1Style === 'outline' || a.btn1Style === 'ghost') && el(RangeControl, {
468 label: __('Border Width', 'blockenberg'),
469 value: a.btn1BorderWidth,
470 min: 1,
471 max: 4,
472 onChange: function (v) { setAttributes({ btn1BorderWidth: v }); }
473 })
474 ),
475
476 // ── Button 2 ──────────────────────────────────────────────────
477 el(PanelBody, { title: __('Button 2 (Secondary)', 'blockenberg'), initialOpen: false },
478 el(ToggleControl, {
479 label: __('Show Second Button', 'blockenberg'),
480 checked: a.btn2Enabled,
481 __nextHasNoMarginBottom: true,
482 onChange: function (v) { setAttributes({ btn2Enabled: v }); }
483 }),
484 a.btn2Enabled && el(Fragment, {},
485 el(TextControl, {
486 label: __('Button Text', 'blockenberg'),
487 value: a.btn2Text,
488 onChange: function (v) { setAttributes({ btn2Text: v }); }
489 }),
490 el(TextControl, {
491 label: __('URL', 'blockenberg'),
492 value: a.btn2Url,
493 type: 'url',
494 placeholder: 'https://',
495 onChange: function (v) { setAttributes({ btn2Url: v }); }
496 }),
497 el(SelectControl, {
498 label: __('Open In', 'blockenberg'),
499 value: a.btn2Target,
500 options: targetOptions,
501 onChange: function (v) { setAttributes({ btn2Target: v }); }
502 }),
503 el('hr', {}),
504 el(SelectControl, {
505 label: __('Button Style', 'blockenberg'),
506 value: a.btn2Style,
507 options: btnStyleOptions,
508 onChange: function (v) { setAttributes({ btn2Style: v }); }
509 }),
510 el(RangeControl, {
511 label: __('Border Radius', 'blockenberg'),
512 value: a.btn2Radius,
513 min: 0,
514 max: 50,
515 onChange: function (v) { setAttributes({ btn2Radius: v }); }
516 }),
517 el(RangeControl, {
518 label: __('Padding Vertical', 'blockenberg'),
519 value: a.btn2PaddingV,
520 min: 4,
521 max: 36,
522 onChange: function (v) { setAttributes({ btn2PaddingV: v }); }
523 }),
524 el(RangeControl, {
525 label: __('Padding Horizontal', 'blockenberg'),
526 value: a.btn2PaddingH,
527 min: 8,
528 max: 80,
529 onChange: function (v) { setAttributes({ btn2PaddingH: v }); }
530 }),
531 (a.btn2Style === 'outline' || a.btn2Style === 'ghost') && el(RangeControl, {
532 label: __('Border Width', 'blockenberg'),
533 value: a.btn2BorderWidth,
534 min: 1,
535 max: 4,
536 onChange: function (v) { setAttributes({ btn2BorderWidth: v }); }
537 })
538 )
539 ),
540
541 // ── Colors ────────────────────────────────────────────────────
542 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
543 el('p', { style: { margin: '4px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __('Text', 'blockenberg')),
544 renderColorControl('headlineColor', __('Headline', 'blockenberg'), a.headlineColor, function (c) { setAttributes({ headlineColor: c }); }),
545 renderColorControl('subColor', __('Sub-headline', 'blockenberg'), a.subColor, function (c) { setAttributes({ subColor: c }); }),
546 el('hr', {}),
547 el('p', { style: { margin: '4px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __('Button 1', 'blockenberg')),
548 renderColorControl('btn1Bg', __('Background', 'blockenberg'), a.btn1Bg, function (c) { setAttributes({ btn1Bg: c }); }),
549 renderColorControl('btn1Color', __('Text', 'blockenberg'), a.btn1Color, function (c) { setAttributes({ btn1Color: c }); }),
550 renderColorControl('btn1BgHover', __('Background (Hover)', 'blockenberg'), a.btn1BgHover, function (c) { setAttributes({ btn1BgHover: c }); }),
551 renderColorControl('btn1ColorHover', __('Text (Hover)', 'blockenberg'), a.btn1ColorHover, function (c) { setAttributes({ btn1ColorHover: c }); }),
552 renderColorControl('btn1BorderColor', __('Border', 'blockenberg'), a.btn1BorderColor, function (c) { setAttributes({ btn1BorderColor: c }); }),
553 a.btn2Enabled && el(Fragment, {},
554 el('hr', {}),
555 el('p', { style: { margin: '4px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, __('Button 2', 'blockenberg')),
556 renderColorControl('btn2Bg', __('Background', 'blockenberg'), a.btn2Bg, function (c) { setAttributes({ btn2Bg: c }); }),
557 renderColorControl('btn2Color', __('Text', 'blockenberg'), a.btn2Color, function (c) { setAttributes({ btn2Color: c }); }),
558 renderColorControl('btn2BgHover', __('Background (Hover)', 'blockenberg'), a.btn2BgHover, function (c) { setAttributes({ btn2BgHover: c }); }),
559 renderColorControl('btn2ColorHover', __('Text (Hover)', 'blockenberg'), a.btn2ColorHover, function (c) { setAttributes({ btn2ColorHover: c }); }),
560 renderColorControl('btn2BorderColor', __('Border', 'blockenberg'), a.btn2BorderColor, function (c) { setAttributes({ btn2BorderColor: c }); })
561 )
562 )
563 );
564
565 // ── data-attributes ────────────────────────────────────────────────────
566 var wrapDataAttrs = {
567 'data-bg-type': a.bgType,
568 'data-text-align': a.textAlign,
569 'data-btn-layout': a.btnLayout,
570 'data-has-overlay': (a.bgType === 'image' && a.bgOverlay) ? 'true' : 'false'
571 };
572
573 // ── Edit render ────────────────────────────────────────────────────────
574 var blockProps = useBlockProps({
575 className: 'bkbg-editor-wrap',
576 'data-block-label': 'Call to Action'
577 });
578
579 return el('div', blockProps,
580 inspector,
581 el('div', Object.assign({ className: 'bkbg-cta-wrap', style: buildWrapStyle(a) }, wrapDataAttrs),
582 // Overlay for image backgrounds
583 a.bgType === 'image' && a.bgOverlay && el('div', {
584 className: 'bkbg-cta-overlay',
585 'aria-hidden': 'true'
586 }),
587
588 // Inner content wrapper (max-width limiter)
589 el('div', { className: 'bkbg-cta-inner' },
590 el(RichText, {
591 tagName: a.titleTag,
592 className: 'bkbg-cta-headline',
593 value: a.headline,
594 onChange: function (v) { setAttributes({ headline: v }); },
595 placeholder: __('Your Headline…', 'blockenberg'),
596 allowedFormats: ['core/bold', 'core/italic', 'core/text-color']
597 }),
598 el(RichText, {
599 tagName: 'p',
600 className: 'bkbg-cta-sub',
601 value: a.subheadline,
602 onChange: function (v) { setAttributes({ subheadline: v }); },
603 placeholder: __('Supporting text…', 'blockenberg'),
604 allowedFormats: ['core/bold', 'core/italic', 'core/link', 'core/text-color']
605 }),
606 el('div', { className: 'bkbg-cta-btns', 'data-layout': a.btnLayout },
607 // Button 1
608 el('span', {
609 className: 'bkbg-cta-btn bkbg-cta-btn1',
610 'data-style': a.btn1Style,
611 title: a.btn1Url ? a.btn1Url : __('(no URL set)', 'blockenberg')
612 }, a.btn1Text || __('Button Text', 'blockenberg')),
613 // Button 2
614 a.btn2Enabled && el('span', {
615 className: 'bkbg-cta-btn bkbg-cta-btn2',
616 'data-style': a.btn2Style,
617 title: a.btn2Url ? a.btn2Url : __('(no URL set)', 'blockenberg')
618 }, a.btn2Text || __('Button Text', 'blockenberg'))
619 )
620 )
621 )
622 );
623 },
624
625 // ── Save ─────────────────────────────────────────────────────────────────
626 save: function (props) {
627 var a = props.attributes;
628 var RichTextContent = wp.blockEditor.RichText.Content;
629
630 var wrapDataAttrs = {
631 'data-bg-type': a.bgType,
632 'data-text-align': a.textAlign,
633 'data-btn-layout': a.btnLayout,
634 'data-has-overlay': (a.bgType === 'image' && a.bgOverlay) ? 'true' : 'false'
635 };
636
637 return el('div', Object.assign({ className: 'bkbg-cta-wrap', style: buildWrapStyle(a) }, wrapDataAttrs),
638 // Overlay
639 a.bgType === 'image' && a.bgOverlay && el('div', {
640 className: 'bkbg-cta-overlay',
641 'aria-hidden': 'true'
642 }),
643
644 // Inner
645 el('div', { className: 'bkbg-cta-inner' },
646 el(RichTextContent, {
647 tagName: a.titleTag,
648 className: 'bkbg-cta-headline',
649 value: a.headline
650 }),
651 el(RichTextContent, {
652 tagName: 'p',
653 className: 'bkbg-cta-sub',
654 value: a.subheadline
655 }),
656 el('div', { className: 'bkbg-cta-btns', 'data-layout': a.btnLayout },
657 // Button 1
658 el('a', {
659 href: a.btn1Url || '#',
660 className: 'bkbg-cta-btn bkbg-cta-btn1',
661 'data-style': a.btn1Style,
662 target: a.btn1Target,
663 rel: a.btn1Target === '_blank' ? 'noopener noreferrer' : undefined
664 }, a.btn1Text || 'Get Started'),
665 // Button 2
666 a.btn2Enabled && el('a', {
667 href: a.btn2Url || '#',
668 className: 'bkbg-cta-btn bkbg-cta-btn2',
669 'data-style': a.btn2Style,
670 target: a.btn2Target,
671 rel: a.btn2Target === '_blank' ? 'noopener noreferrer' : undefined
672 }, a.btn2Text || 'Learn More')
673 )
674 )
675 );
676 }
677 });
678 }() );
679