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 / rich-columns / index.js

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

313 lines 17.8 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 PanelColorSettings = wp.blockEditor.PanelColorSettings;
11 var PanelBody = wp.components.PanelBody;
12 var ToggleControl = wp.components.ToggleControl;
13 var RangeControl = wp.components.RangeControl;
14 var SelectControl = wp.components.SelectControl;
15 var TextControl = wp.components.TextControl;
16 var Button = wp.components.Button;
17
18 var _tc, _tv;
19 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
20 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
21 var IP = function () { return window.bkbgIconPicker; };
22
23 var LAYOUT_OPTIONS = [
24 { label: 'Cards (elevated)', value: 'cards' },
25 { label: 'Bordered', value: 'bordered' },
26 { label: 'Flat (no border)', value: 'flat' },
27 { label: 'Icon top + line', value: 'icon-top-line' },
28 { label: 'Icon left', value: 'icon-left' },
29 ];
30
31 var COUNT_OPTIONS = [
32 { label: '2 columns', value: 2 },
33 { label: '3 columns', value: 3 },
34 { label: '4 columns', value: 4 },
35 ];
36
37 var TAG_OPTIONS = [
38 { label: 'H2', value: 'h2' },
39 { label: 'H3', value: 'h3' },
40 { label: 'H4', value: 'h4' },
41 { label: 'H5', value: 'h5' },
42 { label: 'p (bold)', value: 'p' },
43 ];
44
45 function updateCol(cols, idx, field, val) {
46 return cols.map(function (col, i) {
47 if (i !== idx) return col;
48 var p = {}; p[field] = val;
49 return Object.assign({}, col, p);
50 });
51 }
52
53 function getCardStyle(a, col, isActive) {
54 var base = {
55 padding: a.cardPadding + 'px',
56 borderRadius: a.cardRadius + 'px',
57 boxSizing: 'border-box',
58 flexGrow: 0,
59 flexShrink: 0,
60 flexBasis: 'calc(' + (100 / a.columnCount) + '% - ' + ((a.columnCount - 1) * a.gap / a.columnCount) + 'px)',
61 cursor: 'pointer',
62 outline: isActive ? '3px solid #6366f1' : 'none',
63 outlineOffset: 2,
64 background: col.bgColor || (a.layout === 'flat' || a.layout === 'icon-top-line' ? 'transparent' : (a.cardBg || '#ffffff')),
65 };
66 if (a.layout === 'cards') {
67 base.boxShadow = '0 2px 10px rgba(0,0,0,0.08)';
68 } else if (a.layout === 'bordered' || a.layout === 'icon-left') {
69 base.border = '1px solid ' + a.cardBorder;
70 } else if (a.layout === 'icon-top-line') {
71 base.borderTop = '3px solid ' + (col.accentColor || a.iconColor || '#6366f1');
72 base.paddingTop = (a.cardPadding) + 'px';
73 }
74 return base;
75 }
76
77 function renderColumnPreview(col, i, a, activeIdx, setActiveIdx) {
78 var iconLeft = a.layout === 'icon-left';
79
80 var iconEl = a.showIcon && (col.icon || col.iconDashicon || col.iconImageUrl) && el('div', {
81 style: {
82 width: a.iconBgSize + 'px',
83 height: a.iconBgSize + 'px',
84 borderRadius: a.iconBgRadius + 'px',
85 background: col.accentColor ? col.accentColor + '22' : a.iconBg,
86 display: 'flex',
87 alignItems: 'center',
88 justifyContent: 'center',
89 fontSize: a.iconSize + 'px',
90 flexShrink: 0,
91 }
92 }, IP().buildEditorIcon(col.iconPickerType || 'custom-char', col.icon, col.iconDashicon, col.iconImageUrl, col.iconDashiconColor));
93
94 var textBlock = el('div', { style: { flexGrow: 1 } },
95 el(RichText, {
96 tagName: a.headingTag,
97 value: col.heading,
98 onChange: function (v) { setActiveIdx(i); },
99 onFocus: function () { setActiveIdx(i); },
100 onClick: function (e) { e.stopPropagation(); setActiveIdx(i); },
101 className: 'bkbg-rc-heading',
102 style: { color: a.headingColor, margin: '0 0 10px' },
103 placeholder: 'Column heading…',
104 }),
105 el(RichText, {
106 tagName: 'div',
107 value: col.content,
108 onChange: function (v) { setActiveIdx(i); },
109 onFocus: function () { setActiveIdx(i); },
110 onClick: function (e) { e.stopPropagation(); setActiveIdx(i); },
111 className: 'bkbg-rc-body',
112 style: { color: a.textColor },
113 placeholder: 'Column content…',
114 }),
115 a.showLink && col.linkLabel && el('span', {
116 style: {
117 display: 'inline-block', marginTop: 12,
118 color: a.linkColor, fontSize: 14, fontWeight: 600, textDecoration: 'underline'
119 }
120 }, col.linkLabel + '')
121 );
122
123 return el('div', {
124 key: i,
125 style: getCardStyle(a, col, i === activeIdx),
126 onClick: function () { setActiveIdx(i); },
127 },
128 iconLeft
129 ? el('div', { style: { display: 'flex', gap: 16, alignItems: 'flex-start' } }, iconEl, textBlock)
130 : el(Fragment, null,
131 iconEl,
132 el('div', { style: { height: 14 } }),
133 textBlock
134 )
135 );
136 }
137
138 registerBlockType('blockenberg/rich-columns', {
139 title: __('Rich Columns', 'blockenberg'),
140 icon: 'columns',
141 category: 'bkbg-layout',
142 description: __('Multi-column feature/benefit layout with icons, headings, rich text, and links.', 'blockenberg'),
143
144 edit: function (props) {
145 var a = props.attributes;
146 var set = props.setAttributes;
147 var activeSt = useState(0);
148 var activeIdx = activeSt[0];
149 var setActiveIdx = activeSt[1];
150 var blockProps = useBlockProps((function () {
151 var _tv = getTypoCssVars();
152 var s = {
153 background: a.containerBg || '',
154 padding: a.containerPadding ? a.containerPadding + 'px' : undefined
155 };
156 if (_tv) {
157 Object.assign(s, _tv(a.headingTypo, '--bkrc-ht-'));
158 Object.assign(s, _tv(a.bodyTypo, '--bkrc-bt-'));
159 }
160 return { style: s };
161 })());
162
163 var activeCol = a.columns[activeIdx] || a.columns[0];
164
165 function addColumn() {
166 set({ columns: a.columns.concat([{ icon: '', iconType: 'emoji', iconPickerType: 'custom-char', iconDashicon: '', iconImageUrl: '', heading: 'New Feature', content: '<p>Describe this feature in a sentence or two that is clear and compelling.</p>', linkLabel: 'Learn more', linkUrl: '', linkNewTab: false, bgColor: '', accentColor: '' }]) });
167 setActiveIdx(a.columns.length);
168 }
169
170 function removeColumn(i) {
171 if (a.columns.length <= 1) return;
172 set({ columns: a.columns.filter(function (_, idx) { return idx !== i; }) });
173 setActiveIdx(Math.max(0, activeIdx - 1));
174 }
175
176 return el(Fragment, null,
177 el(InspectorControls, null,
178
179 // Active column
180 el(PanelBody, { title: 'Column ' + (activeIdx + 1) + ' Content', initialOpen: true },
181 el('p', { style: { fontSize: 11, color: '#9ca3af', margin: '0 0 8px' } }, 'Click a column in the editor to select it.'),
182 el(IP().IconPickerControl, IP().iconPickerProps(activeCol, function (key, val) { set({ columns: updateCol(a.columns, activeIdx, key, val) }); }, { label: 'Icon', typeAttr: 'iconPickerType', dashiconAttr: 'iconDashicon', imageUrlAttr: 'iconImageUrl' })),
183 el('div', { style: { height: 8 } }),
184 el(TextControl, { label: 'Heading', value: activeCol.heading, onChange: function (v) { set({ columns: updateCol(a.columns, activeIdx, 'heading', v) }); }, __nextHasNoMarginBottom: true }),
185 el('div', { style: { height: 8 } }),
186 el(TextControl, { label: 'Link label (optional)', value: activeCol.linkLabel, onChange: function (v) { set({ columns: updateCol(a.columns, activeIdx, 'linkLabel', v) }); }, __nextHasNoMarginBottom: true }),
187 el('div', { style: { height: 8 } }),
188 el(TextControl, { label: 'Link URL', value: activeCol.linkUrl, onChange: function (v) { set({ columns: updateCol(a.columns, activeIdx, 'linkUrl', v) }); }, __nextHasNoMarginBottom: true }),
189 el('div', { style: { height: 8 } }),
190 el(ToggleControl, { label: 'Open link in new tab', checked: activeCol.linkNewTab, onChange: function (v) { set({ columns: updateCol(a.columns, activeIdx, 'linkNewTab', v) }); }, __nextHasNoMarginBottom: true }),
191 el('div', { style: { height: 12, borderTop: '1px solid #f3f4f6', marginTop: 8 } }),
192 el('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
193 a.columns.map(function (col, i) {
194 return el(Button, {
195 key: i,
196 variant: i === activeIdx ? 'primary' : 'secondary',
197 size: 'small',
198 onClick: function () { setActiveIdx(i); },
199 __nextHasNoMarginBottom: true,
200 }, col.icon + ' ' + (i + 1));
201 }),
202 el(Button, { onClick: addColumn, variant: 'tertiary', size: 'small', __nextHasNoMarginBottom: true }, '+ Add'),
203 el(Button, { onClick: function () { removeColumn(activeIdx); }, isDestructive: true, variant: 'tertiary', size: 'small', __nextHasNoMarginBottom: true }, 'Remove')
204 )
205 ),
206
207 // Layout
208 el(PanelBody, { title: 'Layout', initialOpen: false },
209 el(SelectControl, { label: 'Column count', value: a.columnCount, options: COUNT_OPTIONS, onChange: function (v) { set({ columnCount: parseInt(v, 10) }); }, __nextHasNoMarginBottom: true }),
210 el('div', { style: { height: 10 } }),
211 el(SelectControl, { label: 'Card style', value: a.layout, options: LAYOUT_OPTIONS, onChange: function (v) { set({ layout: v }); }, __nextHasNoMarginBottom: true }),
212 el('div', { style: { height: 10 } }),
213 el(RangeControl, { label: 'Column gap (px)', value: a.gap, min: 8, max: 72, onChange: function (v) { set({ gap: v }); }, __nextHasNoMarginBottom: true }),
214 el('div', { style: { height: 10 } }),
215 el(RangeControl, { label: 'Card padding (px)', value: a.cardPadding, min: 12, max: 60, onChange: function (v) { set({ cardPadding: v }); }, __nextHasNoMarginBottom: true }),
216 el('div', { style: { height: 10 } }),
217 el(RangeControl, { label: 'Card radius (px)', value: a.cardRadius, min: 0, max: 32, onChange: function (v) { set({ cardRadius: v }); }, __nextHasNoMarginBottom: true }),
218 el('div', { style: { height: 10 } }),
219 el(RangeControl, { label: 'Max width (px)', value: a.maxWidth, min: 400, max: 1600, onChange: function (v) { set({ maxWidth: v }); }, __nextHasNoMarginBottom: true }),
220 el('div', { style: { height: 10 } }),
221 el(RangeControl, { label: 'Container padding (px)', value: a.containerPadding, min: 0, max: 100, onChange: function (v) { set({ containerPadding: v }); }, __nextHasNoMarginBottom: true }),
222 el('div', { style: { height: 10 } }),
223 el(ToggleControl, { label: 'Show icon', checked: a.showIcon, onChange: function (v) { set({ showIcon: v }); }, __nextHasNoMarginBottom: true }),
224 el('div', { style: { height: 8 } }),
225 el(ToggleControl, { label: 'Show link', checked: a.showLink, onChange: function (v) { set({ showLink: v }); }, __nextHasNoMarginBottom: true }),
226 el('div', { style: { height: 8 } }),
227 el(ToggleControl, { label: 'Show dividers between columns', checked: a.showDivider, onChange: function (v) { set({ showDivider: v }); }, __nextHasNoMarginBottom: true })
228 ),
229
230 // Typography
231 el(PanelBody, { title: 'Typography', initialOpen: false },
232 el(SelectControl, { label: 'Heading tag', value: a.headingTag, options: TAG_OPTIONS, onChange: function (v) { set({ headingTag: v }); }, __nextHasNoMarginBottom: true }),
233 el('div', { style: { height: 10 } }),
234 (function () { var TC = getTypoControl(); return TC ? [
235 el(TC, { key: 'ht', label: 'Heading', value: a.headingTypo, onChange: function (v) { set({ headingTypo: v }); } }),
236 el(TC, { key: 'bt', label: 'Body Text', value: a.bodyTypo, onChange: function (v) { set({ bodyTypo: v }); } }),
237 ] : null; })(),
238 el(RangeControl, { label: 'Icon font size (px)', value: a.iconSize, min: 16, max: 56, onChange: function (v) { set({ iconSize: v }); }, __nextHasNoMarginBottom: true })
239 ),
240
241 // Icon appearance
242 el(PanelBody, { title: 'Icon Appearance', initialOpen: false },
243 el('div', { style: { height: 10 } }),
244 el(RangeControl, { label: 'Icon box size (px)', value: a.iconBgSize, min: 32, max: 96, onChange: function (v) { set({ iconBgSize: v }); }, __nextHasNoMarginBottom: true }),
245 el('div', { style: { height: 10 } }),
246 el(RangeControl, { label: 'Icon box radius (px)', value: a.iconBgRadius, min: 0, max: 48, onChange: function (v) { set({ iconBgRadius: v }); }, __nextHasNoMarginBottom: true })
247 ),
248
249 // Colors
250 el(PanelColorSettings, {
251 title: 'Colors',
252 initialOpen: false,
253 colorSettings: [
254 { label: 'Container Background', value: a.containerBg, onChange: function (v) { set({ containerBg: v || '' }); } },
255 { label: 'Card Background', value: a.cardBg, onChange: function (v) { set({ cardBg: v || '#ffffff' }); } },
256 { label: 'Card Border', value: a.cardBorder, onChange: function (v) { set({ cardBorder: v || '#e5e7eb' }); } },
257 { label: 'Heading Color', value: a.headingColor, onChange: function (v) { set({ headingColor: v || '#111827' }); } },
258 { label: 'Body Text Color', value: a.textColor, onChange: function (v) { set({ textColor: v || '#374151' }); } },
259 { label: 'Link Color', value: a.linkColor, onChange: function (v) { set({ linkColor: v || '#6366f1' }); } },
260 { label: 'Icon Background', value: a.iconBg, onChange: function (v) { set({ iconBg: v || '#ede9fe' }); } },
261 { label: 'Divider Color', value: a.dividerColor, onChange: function (v) { set({ dividerColor: v || '#e5e7eb' }); } },
262 ]
263 })
264 ),
265
266 // ── Editor Preview ──
267 el('div', blockProps,
268 el('div', {
269 style: {
270 maxWidth: a.maxWidth + 'px',
271 margin: '0 auto',
272 display: 'flex',
273 gap: a.gap + 'px',
274 flexWrap: 'wrap',
275 alignItems: 'stretch',
276 }
277 },
278 a.columns.map(function (col, i) {
279 return el(Fragment, { key: i },
280 i > 0 && a.showDivider && a.layout === 'flat' && el('div', {
281 style: { width: 1, background: a.dividerColor, flexShrink: 0, alignSelf: 'stretch' }
282 }),
283 renderColumnPreview(col, i, a, activeIdx, setActiveIdx)
284 );
285 }),
286 // Add column button
287 a.columns.length < 4 && el('button', {
288 onClick: addColumn,
289 style: {
290 border: '2px dashed #d1d5db',
291 borderRadius: a.cardRadius + 'px',
292 padding: a.cardPadding + 'px',
293 background: 'transparent',
294 cursor: 'pointer',
295 color: '#9ca3af',
296 fontSize: 13,
297 flexBasis: 'calc(' + (100 / a.columnCount) + '% - ' + ((a.columnCount - 1) * a.gap / a.columnCount) + 'px)',
298 flexShrink: 0,
299 }
300 }, '+ Add Column')
301 )
302 )
303 );
304 },
305
306 save: function (props) {
307 return el('div', useBlockProps.save(),
308 el('div', { className: 'bkbg-rc-app', 'data-opts': JSON.stringify(props.attributes) })
309 );
310 }
311 });
312 }() );
313