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 / feature-alternating / index.js

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

257 lines 17.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* ====================================================
2 Feature Alternating — editor (index.js)
3 Block: blockenberg/feature-alternating
4 ==================================================== */
5 ( function () {
6 var el = wp.element.createElement;
7 var useState = wp.element.useState;
8 var Fragment = wp.element.Fragment;
9 var RichText = wp.blockEditor.RichText;
10 var MediaUpload = wp.blockEditor.MediaUpload;
11 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
12 var registerBlockType = wp.blocks.registerBlockType;
13 var InspectorControls = wp.blockEditor.InspectorControls;
14 var useBlockProps = wp.blockEditor.useBlockProps;
15 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
16 var PanelBody = wp.components.PanelBody;
17 var RangeControl = wp.components.RangeControl;
18 var SelectControl = wp.components.SelectControl;
19 var ToggleControl = wp.components.ToggleControl;
20 var TextControl = wp.components.TextControl;
21 var Button = wp.components.Button;
22 var ColorPicker = wp.components.ColorPicker;
23 var Popover = wp.components.Popover;
24 var __ = wp.i18n.__;
25
26 var _faltTC, _faltTV;
27 function _tc() { return _faltTC || (_faltTC = window.bkbgTypographyControl); }
28 function _tv() { return _faltTV || (_faltTV = window.bkbgTypoCssVars); }
29
30 function uid() { return Math.random().toString(36).slice(2,9); }
31
32 function isReversed(idx, imageSide) {
33 if (imageSide === 'always-right') return true;
34 if (imageSide === 'always-left') return false;
35 return idx % 2 !== 0;
36 }
37
38 function wrapStyle(a) {
39 return Object.assign({
40 '--bkbg-falt-pt': a.paddingTop + 'px',
41 '--bkbg-falt-pb': a.paddingBottom + 'px',
42 '--bkbg-falt-sec-bg':a.sectionBg,
43 '--bkbg-falt-ttl-c': a.titleColor,
44 '--bkbg-falt-desc-c':a.descColor,
45 '--bkbg-falt-feat-c':a.featureLabelColor,
46 '--bkbg-falt-ph-bg': a.imagePlaceholderBg,
47 '--bkbg-falt-r': a.imageRadius + 'px',
48 '--bkbg-falt-row-gap':a.rowGap + 'px',
49 '--bkbg-falt-col-gap':a.colGap + 'px',
50 '--bkbg-falt-icon-sz':a.featureIconSize + 'px',
51 }, _tv()(a.typoEyebrow, '--bkbg-falt-eb-'), _tv()(a.typoTitle, '--bkbg-falt-tt-'), _tv()(a.typoDescription, '--bkbg-falt-ds-'), _tv()(a.typoFeatureLabel, '--bkbg-falt-fl-'));
52 }
53
54 /* ----- Inspector row for one alternating row ----- */
55 function RowInspector(props) {
56 var r = props.row;
57 var idx = props.index;
58 var total = props.total;
59 var onUpdate = props.onUpdate;
60 var onDelete = props.onDelete;
61 var onMove = props.onMove;
62 var open = useState(idx === 0);
63 var isOpen = open[0]; var setOpen = open[1];
64
65 return el('div', { style:{ borderBottom:'1px solid #e2e8f0', marginBottom:4 } },
66 el('div', { style:{ display:'flex', alignItems:'center', cursor:'pointer', padding:'6px 0' }, onClick:function(){ setOpen(!isOpen); } },
67 el('span', { style:{ flex:1, fontWeight:600, fontSize:12 } }, '#'+(idx+1)+' '+(r.title||'Row')),
68 el('span', null, isOpen ? '' : '')
69 ),
70 !isOpen ? null : el('div', { style:{ paddingBottom:8 } },
71 /* Image upload */
72 el('p', { style:{ fontWeight:600, fontSize:11, marginBottom:4 } }, __('Image','blockenberg')),
73 el(MediaUploadCheck, null,
74 el(MediaUpload, {
75 onSelect: function(media){ onUpdate(idx,{imageUrl:media.url, imageId:media.id}); },
76 allowedTypes:['image'],
77 value: r.imageId,
78 render: function(p) {
79 return el('div', null,
80 el(Button, { variant:'secondary', isSmall:true, onClick:p.open }, r.imageUrl ? __('Change image','blockenberg') : __('Select image','blockenberg')),
81 r.imageUrl ? el(Button, { isDestructive:true, isSmall:true, style:{marginLeft:4}, onClick:function(){ onUpdate(idx,{imageUrl:'',imageId:0}); } }, __('Remove','blockenberg')) : null
82 );
83 }
84 })
85 ),
86 r.imageUrl ? el('img', { src:r.imageUrl, style:{ marginTop:6, maxWidth:'100%', borderRadius:4 } }) : null,
87 el(TextControl, { label:__('Eyebrow text','blockenberg'), value:r.eyebrow, onChange:function(v){ onUpdate(idx,{eyebrow:v}); } }),
88 el(TextControl, { label:__('Title','blockenberg'), value:r.title, onChange:function(v){ onUpdate(idx,{title:v}); } }),
89 el(TextControl, { label:__('Description','blockenberg'), value:r.description, onChange:function(v){ onUpdate(idx,{description:v}); } }),
90 el(BkbgColorSwatch, { label:__('Accent color','blockenberg'), value:r.accentColor, onChange:function(v){ onUpdate(idx,{accentColor:v}); } }),
91 el('p', { style:{ fontWeight:600, fontSize:11, margin:'8px 0 4px' } }, __('Features','blockenberg')),
92 r.features.map(function(f, fi) {
93 return el('div', { key:f.id, style:{ display:'flex', gap:4, marginBottom:4, alignItems:'center' } },
94 el(TextControl, { value:f.icon, style:{ width:44 }, onChange:function(v){ var feats=[].concat(r.features); feats[fi]=Object.assign({},f,{icon:v}); onUpdate(idx,{features:feats}); }, placeholder:'' }),
95 el(TextControl, { value:f.label, onChange:function(v){ var feats=[].concat(r.features); feats[fi]=Object.assign({},f,{label:v}); onUpdate(idx,{features:feats}); }, placeholder:'Feature…' }),
96 el(Button, { isSmall:true, isDestructive:true, onClick:function(){ var feats=r.features.filter(function(_,i){ return i!==fi; }); onUpdate(idx,{features:feats}); } }, '')
97 );
98 }),
99 el(Button, { isSmall:true, variant:'tertiary', onClick:function(){ onUpdate(idx,{ features:r.features.concat([{id:uid(),icon:'',label:''}]) }); } }, __('+ Feature','blockenberg')),
100 el(TextControl, { label:__('CTA label','blockenberg'), value:r.ctaLabel, onChange:function(v){ onUpdate(idx,{ctaLabel:v}); } }),
101 el(TextControl, { label:__('CTA URL','blockenberg'), value:r.ctaUrl, onChange:function(v){ onUpdate(idx,{ctaUrl:v}); } }),
102 el(SelectControl, { label:__('CTA target','blockenberg'), value:r.ctaTarget, options:[{label:'Same tab',value:'_self'},{label:'New tab',value:'_blank'}], onChange:function(v){ onUpdate(idx,{ctaTarget:v}); } }),
103 el(ToggleControl, { label:__('Show CTA','blockenberg'), checked:r.showCta, onChange:function(v){ onUpdate(idx,{showCta:v}); } }),
104 el('div', { style:{ display:'flex', gap:4, marginTop:8 } },
105 el(Button, { isSmall:true, variant:'secondary', disabled:idx===0, onClick:function(){ onMove(idx,idx-1); } }, ''),
106 el(Button, { isSmall:true, variant:'secondary', disabled:idx===total-1, onClick:function(){ onMove(idx,idx+1); } }, ''),
107 el(Button, { isSmall:true, isDestructive:true, onClick:function(){ onDelete(idx); } }, __('Delete','blockenberg'))
108 )
109 )
110 );
111 }
112
113 var BkbgColorSwatch = function (props) {
114 var _st = useState(false); var isOpen = _st[0]; var setOpen = _st[1];
115 return el(Fragment, {},
116 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' } },
117 el('span', { style: { fontSize: '11px', fontWeight: 500, textTransform: 'uppercase', color: '#1e1e1e' } }, props.label),
118 el('button', {
119 type: 'button',
120 onClick: function () { setOpen(!isOpen); },
121 style: { width: '28px', height: '28px', borderRadius: '4px', border: '1px solid #ccc', background: props.value || '#ffffff', cursor: 'pointer', padding: 0 }
122 })
123 ),
124 isOpen && el(Popover, { onClose: function () { setOpen(false); } },
125 el('div', { style: { padding: '8px' } },
126 el(ColorPicker, { color: props.value, onChangeComplete: function (c) { props.onChange(c.hex); }, enableAlpha: true })
127 )
128 )
129 );
130 };
131
132 registerBlockType('blockenberg/feature-alternating', {
133 edit: function (props) {
134 var a = props.attributes;
135 var setAttr = props.setAttributes;
136
137 function updateRow(idx, patch) {
138 var arr = a.rows.map(function(r,i){ return i===idx ? Object.assign({},r,patch) : r; });
139 setAttr({ rows: arr });
140 }
141 function deleteRow(idx) { setAttr({ rows: a.rows.filter(function(_,i){ return i!==idx; }) }); }
142 function moveRow(from, to) {
143 var arr = a.rows.slice();
144 arr.splice(to, 0, arr.splice(from, 1)[0]);
145 setAttr({ rows: arr });
146 }
147 function addRow() {
148 setAttr({ rows: a.rows.concat([{
149 id:uid(), imageUrl:'', imageId:0,
150 eyebrow:'What you can do', title:'New Feature', description:'Describe this feature in detail.',
151 features:[{id:uid(),icon:'',label:'First benefit'},{id:uid(),icon:'',label:'Second benefit'}],
152 ctaLabel:'Learn more', ctaUrl:'#', ctaTarget:'_self', showCta:true, accentColor:'#6c3fb5'
153 }]) });
154 }
155
156 var blockProps = useBlockProps({ className:'bkbg-falt-wrap', style:wrapStyle(a) });
157
158 var inspector = el(InspectorControls, null,
159 el(PanelBody, { title:__('Rows','blockenberg'), initialOpen:true },
160 a.rows.map(function(r,i){ return el(RowInspector, { key:r.id, row:r, index:i, total:a.rows.length, onUpdate:updateRow, onDelete:deleteRow, onMove:moveRow }); }),
161 el(Button, { variant:'primary', onClick:addRow, style:{marginTop:8} }, __('+ Add row','blockenberg'))
162 ),
163 el(PanelBody, { title:__('Layout','blockenberg'), initialOpen:false },
164 el(SelectControl, { label:__('Image side','blockenberg'), value:a.imageSide, options:[{label:'Auto (alternating)',value:'auto'},{label:'Always left',value:'always-left'},{label:'Always right',value:'always-right'}], onChange:function(v){ setAttr({imageSide:v}); } }),
165 el(RangeControl, { label:__('Image radius (px)','blockenberg'), value:a.imageRadius, min:0, max:32, onChange:function(v){ setAttr({imageRadius:v}); } }),
166 el(ToggleControl, { label:__('Image shadow','blockenberg'), checked:a.imageShadow, onChange:function(v){ setAttr({imageShadow:v}); } }),
167 el(RangeControl, { label:__('Row gap (px)','blockenberg'), value:a.rowGap, min:0, max:200, onChange:function(v){ setAttr({rowGap:v}); } }),
168 el(RangeControl, { label:__('Column gap (px)','blockenberg'),value:a.colGap, min:0, max:128, onChange:function(v){ setAttr({colGap:v}); } }),
169 el(ToggleControl, { label:__('Show eyebrow','blockenberg'), checked:a.showEyebrow, onChange:function(v){ setAttr({showEyebrow:v}); } }),
170 el(ToggleControl, { label:__('Show features','blockenberg'), checked:a.showFeatures, onChange:function(v){ setAttr({showFeatures:v}); } })
171 ),
172 el(PanelBody, { title:__('Typography','blockenberg'), initialOpen:false },
173 _tc() && el(_tc(), { label: __('Eyebrow', 'blockenberg'), value: a.typoEyebrow, onChange: function (v) { setAttr({ typoEyebrow: v }); } }),
174 _tc() && el(_tc(), { label: __('Title', 'blockenberg'), value: a.typoTitle, onChange: function (v) { setAttr({ typoTitle: v }); } }),
175 _tc() && el(_tc(), { label: __('Description', 'blockenberg'), value: a.typoDescription, onChange: function (v) { setAttr({ typoDescription: v }); } }),
176 _tc() && el(_tc(), { label: __('Feature Label', 'blockenberg'), value: a.typoFeatureLabel, onChange: function (v) { setAttr({ typoFeatureLabel: v }); } }),
177 el(RangeControl, { label:__('Feature icon size (px)','blockenberg'), value:a.featureIconSize, min:14, max:32, onChange:function(v){ setAttr({featureIconSize:v}); } })
178 ),
179 el(PanelBody, { title:__('Spacing','blockenberg'), initialOpen:false },
180 el(RangeControl, { label:__('Padding top (px)','blockenberg'), value:a.paddingTop, min:0, max:200, onChange:function(v){ setAttr({paddingTop:v}); } }),
181 el(RangeControl, { label:__('Padding bottom (px)','blockenberg'), value:a.paddingBottom, min:0, max:200, onChange:function(v){ setAttr({paddingBottom:v}); } })
182 ),
183 el(PanelColorSettings, { title:__('Colors','blockenberg'), initialOpen:false, colorSettings:[
184 {label:__('Section bg','blockenberg'), value:a.sectionBg, onChange:function(v){ setAttr({sectionBg:v||''}); }},
185 {label:__('Title','blockenberg'), value:a.titleColor, onChange:function(v){ setAttr({titleColor:v||''}); }},
186 {label:__('Description','blockenberg'), value:a.descColor, onChange:function(v){ setAttr({descColor:v||''}); }},
187 {label:__('Feature label','blockenberg'), value:a.featureLabelColor, onChange:function(v){ setAttr({featureLabelColor:v||''}); }},
188 {label:__('Image placeholder','blockenberg'), value:a.imagePlaceholderBg, onChange:function(v){ setAttr({imagePlaceholderBg:v||''}); }},
189 ] })
190 );
191
192 /* canvas rendering */
193 function renderRow(r, idx) {
194 var reversed = isReversed(idx, a.imageSide);
195 var imgCol = el('div', { className:'bkbg-falt-col bkbg-falt-col--image' },
196 r.imageUrl
197 ? el('img', { src:r.imageUrl, alt:'', className:'bkbg-falt-img' + (a.imageShadow ? ' bkbg-falt-img--shadow' : '') })
198 : el('div', { className:'bkbg-falt-placeholder' }, el('span', null, '🖼'))
199 );
200 var textCol = el('div', { className:'bkbg-falt-col bkbg-falt-col--text', style:{'--bkbg-falt-accent': r.accentColor} },
201 a.showEyebrow && r.eyebrow ? el('span', { className:'bkbg-falt-eyebrow' }, r.eyebrow) : null,
202 el('div', { className:'bkbg-falt-title' }, r.title),
203 el('div', { className:'bkbg-falt-desc' }, r.description),
204 a.showFeatures && r.features.length ? el('ul', { className:'bkbg-falt-features' },
205 r.features.map(function(f){ return el('li', { key:f.id, className:'bkbg-falt-feature' },
206 el('span', { className:'bkbg-falt-feat-icon' }, f.icon),
207 el('span', { className:'bkbg-falt-feat-label' }, f.label)
208 ); })
209 ) : null,
210 r.showCta ? el('a', { href:'#', className:'bkbg-falt-cta', onClick:function(e){ e.preventDefault(); } }, r.ctaLabel, '') : null
211 );
212 return el('div', { key:r.id, className:'bkbg-falt-row' + (reversed ? ' bkbg-falt-row--reverse' : '') },
213 reversed ? [textCol, imgCol] : [imgCol, textCol]
214 );
215 }
216
217 return el(Fragment, null,
218 inspector,
219 el('div', blockProps,
220 a.rows.map(function(r, i){ return renderRow(r, i); })
221 )
222 );
223 },
224
225 save: function (props) {
226 var a = props.attributes;
227 var blockProps = wp.blockEditor.useBlockProps.save({ className:'bkbg-falt-wrap', style:wrapStyle(a) });
228
229 return el('div', blockProps,
230 a.rows.map(function(r, idx) {
231 var reversed = isReversed(idx, a.imageSide);
232 var imgCol = el('div', { className:'bkbg-falt-col bkbg-falt-col--image' },
233 r.imageUrl
234 ? el('img', { src:r.imageUrl, alt:'', className:'bkbg-falt-img' + (a.imageShadow ? ' bkbg-falt-img--shadow' : '') })
235 : el('div', { className:'bkbg-falt-placeholder' }, el('span', null, '🖼'))
236 );
237 var textCol = el('div', { className:'bkbg-falt-col bkbg-falt-col--text', style:{'--bkbg-falt-accent':r.accentColor} },
238 a.showEyebrow && r.eyebrow ? el('span', { className:'bkbg-falt-eyebrow' }, r.eyebrow) : null,
239 el(RichText.Content, { tagName:'h3', className:'bkbg-falt-title', value:r.title }),
240 el(RichText.Content, { tagName:'p', className:'bkbg-falt-desc', value:r.description }),
241 a.showFeatures && r.features.length ? el('ul', { className:'bkbg-falt-features' },
242 r.features.map(function(f){ return el('li', { key:f.id, className:'bkbg-falt-feature' },
243 el('span', { className:'bkbg-falt-feat-icon' }, f.icon),
244 el('span', { className:'bkbg-falt-feat-label' }, f.label)
245 ); })
246 ) : null,
247 r.showCta ? el('a', { href:r.ctaUrl, className:'bkbg-falt-cta', target:r.ctaTarget, rel:r.ctaTarget==='_blank'?'noopener noreferrer':undefined }, r.ctaLabel, '') : null
248 );
249 return el('div', { key:r.id, className:'bkbg-falt-row' + (reversed ? ' bkbg-falt-row--reverse' : '') },
250 reversed ? [textCol, imgCol] : [imgCol, textCol]
251 );
252 })
253 );
254 }
255 });
256 }() );
257