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 / side-menu / index.js

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

401 lines 22.9 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 PanelColorSettings = wp.blockEditor.PanelColorSettings;
10 var PanelBody = wp.components.PanelBody;
11 var ToggleControl = wp.components.ToggleControl;
12 var RangeControl = wp.components.RangeControl;
13 var SelectControl = wp.components.SelectControl;
14 var TextControl = wp.components.TextControl;
15 var Button = wp.components.Button;
16 var ColorPicker = wp.components.ColorPicker;
17 var Popover = wp.components.Popover;
18
19 var _tc, _tv;
20 function getTC() { return _tc || (_tc = window.bkbgTypographyControl); }
21 function getTV() { return _tv || (_tv = window.bkbgTypoCssVars); }
22 var IP = function () { return window.bkbgIconPicker; };
23
24 /* ─── helpers ─── */
25 function updateItem(arr, idx, field, val) {
26 return arr.map(function (item, i) {
27 if (i !== idx) return item;
28 var p = {}; p[field] = val;
29 return Object.assign({}, item, p);
30 });
31 }
32
33 function moveItem(arr, from, to) {
34 var a = arr.slice();
35 var item = a.splice(from, 1)[0];
36 a.splice(to, 0, item);
37 return a;
38 }
39
40 /* ─── ItemEditor ─── */
41 function ItemEditor(props) {
42 var items = props.items;
43 var onChange = props.onChange;
44 var activeIdx = props.activeIdx;
45 var setActiveIdx = props.setActiveIdx;
46
47 var levelLabels = ['Level 0 — Section heading', 'Level 1 — Nav item', 'Level 2 — Nested item'];
48
49 function addItem() {
50 onChange(items.concat([{
51 label: 'New Item', url: '#', icon: '', iconType: 'custom-char', iconDashicon: '', iconImageUrl: '', level: 1,
52 badge: '', badgeColor: '#6366f1', dividerBefore: false
53 }]));
54 }
55
56 return el(Fragment, null,
57 items.map(function (item, idx) {
58 var isActive = idx === activeIdx;
59 var indent = item.level * 12;
60
61 return el('div', {
62 key: idx,
63 style: {
64 marginBottom: '4px',
65 border: '1px solid ' + (isActive ? '#6366f1' : '#e5e7eb'),
66 borderRadius: '6px',
67 overflow: 'hidden'
68 }
69 },
70 /* header row */
71 el('div', {
72 style: {
73 display: 'flex', alignItems: 'center', gap: '6px',
74 padding: '6px 8px', cursor: 'pointer',
75 background: isActive ? '#f0f0ff' : '#f9fafb',
76 paddingLeft: (8 + indent) + 'px'
77 },
78 onClick: function () { setActiveIdx(isActive ? -1 : idx); }
79 },
80 el('span', { style: { flex: 1, fontSize: '12px', fontWeight: '600', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
81 (item.icon && (item.iconType || 'custom-char') === 'custom-char' ? item.icon + ' ' : '') + (item.label || 'Item ' + (idx + 1))
82 ),
83 el('span', { style: { fontSize: '10px', color: '#9ca3af', flexShrink: 0 } }, 'L' + item.level),
84 el(Button, { icon: 'arrow-up-alt2', isSmall: true, disabled: idx === 0, onClick: function (e) { e.stopPropagation(); onChange(moveItem(items, idx, idx - 1)); setActiveIdx(idx - 1); } }),
85 el(Button, { icon: 'arrow-down-alt2', isSmall: true, disabled: idx === items.length - 1, onClick: function (e) { e.stopPropagation(); onChange(moveItem(items, idx, idx + 1)); setActiveIdx(idx + 1); } }),
86 el(Button, { icon: 'no-alt', isSmall: true, isDestructive: true, onClick: function (e) { e.stopPropagation(); var a = items.slice(); a.splice(idx, 1); onChange(a); setActiveIdx(-1); } })
87 ),
88
89 /* expanded fields */
90 isActive && el('div', { style: { padding: '10px', display: 'flex', flexDirection: 'column', gap: '8px', background: '#fff' } },
91 el(TextControl, { label: __('Label', 'blockenberg'), value: item.label, onChange: function (v) { onChange(updateItem(items, idx, 'label', v)); }, __nextHasNoMarginBottom: true }),
92 el(TextControl, { label: __('URL', 'blockenberg'), value: item.url, onChange: function (v) { onChange(updateItem(items, idx, 'url', v)); }, __nextHasNoMarginBottom: true }),
93 el(IP().IconPickerControl, IP().iconPickerProps(item, function (patch) { var newItems = items.slice(); newItems[idx] = Object.assign({}, item, patch); onChange(newItems); }, { label: __('Icon', 'blockenberg'), charAttr: 'icon', typeAttr: 'iconType', dashiconAttr: 'iconDashicon', imageUrlAttr: 'iconImageUrl' })),
94 el(SelectControl, {
95 label: __('Level', 'blockenberg'),
96 value: String(item.level),
97 options: [
98 { value: '0', label: 'Level 0 — Section heading' },
99 { value: '1', label: 'Level 1 — Nav item' },
100 { value: '2', label: 'Level 2 — Nested item' }
101 ],
102 onChange: function (v) { onChange(updateItem(items, idx, 'level', parseInt(v, 10))); },
103 __nextHasNoMarginBottom: true
104 }),
105 el(TextControl, { label: __('Badge Text', 'blockenberg'), value: item.badge || '', onChange: function (v) { onChange(updateItem(items, idx, 'badge', v)); }, __nextHasNoMarginBottom: true }),
106 item.badge && el(BkbgColorSwatch, { label: __('Badge Color', 'blockenberg'), value: item.badgeColor || '#6366f1', onChange: function (v) { onChange(updateItem(items, idx, 'badgeColor', v)); } }),
107 el(ToggleControl, { label: __('Divider Before', 'blockenberg'), checked: !!item.dividerBefore, onChange: function (v) { onChange(updateItem(items, idx, 'dividerBefore', v)); }, __nextHasNoMarginBottom: true })
108 )
109 );
110 }),
111
112 el(Button, {
113 variant: 'secondary',
114 onClick: addItem,
115 style: { marginTop: '8px', width: '100%', justifyContent: 'center' }
116 }, __('+ Add Item', 'blockenberg'))
117 );
118 }
119
120 /* ─── MenuPreview ─── */
121 function MenuPreview(props) {
122 var a = props.attributes;
123 var onSetActive = props.onSetActive;
124 var openSectionsState = useState(function () {
125 var s = {};
126 a.items.forEach(function (item, idx) { if (item.level === 0 && a.defaultExpanded) s[idx] = true; });
127 return s;
128 });
129 var openSections = openSectionsState[0];
130 var setOpenSections = openSectionsState[1];
131
132 function toggleSection(idx) {
133 var ns = Object.assign({}, openSections);
134 ns[idx] = !ns[idx];
135 setOpenSections(ns);
136 }
137
138 /* find the most recent L0 section for each item */
139 var sectionMap = {};
140 var lastL0 = -1;
141 a.items.forEach(function (item, idx) {
142 if (item.level === 0) { lastL0 = idx; sectionMap[idx] = idx; }
143 else { sectionMap[idx] = lastL0; }
144 });
145
146 function isVisible(idx) {
147 var item = a.items[idx];
148 if (item.level === 0) return true;
149 var secIdx = sectionMap[idx];
150 if (secIdx < 0) return true;
151 if (!a.collapsible) return true;
152 return !!openSections[secIdx];
153 }
154
155 var menuStyle = {
156 width: '100%',
157 background: a.menuBg || '#fff',
158 border: '1px solid ' + (a.menuBorder || '#e5e7eb'),
159 borderRadius: (a.borderRadius || 8) + 'px',
160 overflow: 'hidden',
161 boxShadow: '0 1px 4px rgba(0,0,0,0.05)'
162 };
163
164 return el('div', { style: menuStyle },
165 /* heading */
166 a.showHeading && a.headingText && el('div', {
167 style: {
168 padding: (a.itemPaddingV || 8) + 'px ' + (a.itemPaddingH || 14) + 'px',
169 fontWeight: a.headingFontWeight||700,
170 fontSize: (a.headingSize || 11) + 'px',
171 textTransform: 'uppercase',
172 letterSpacing: '0.07em',
173 color: a.headingColor || '#9ca3af',
174 borderBottom: '1px solid ' + (a.dividerColor || '#e5e7eb')
175 }
176 }, a.headingText),
177
178 /* items */
179 el('nav', { style: { padding: '6px 0' } },
180 a.items.map(function (item, idx) {
181 if (!isVisible(idx)) return null;
182
183 var isSection = item.level === 0;
184 var isActive = idx === a.activeIndex;
185 var indent = item.level * 12;
186 var hasChildren = !isSection && false; /* computed below */
187
188 /* check if this L0 has children to show collapse arrow */
189 var hasChildItems = isSection && a.items.some(function (it, i) { return i > idx && it.level > 0 && sectionMap[i] === idx; });
190
191 var itemStyle = {
192 display: 'flex',
193 alignItems: 'center',
194 gap: '8px',
195 padding: (a.itemPaddingV || 8) + 'px ' + (a.itemPaddingH || 14) + 'px',
196 paddingLeft: ((a.itemPaddingH || 14) + indent) + 'px',
197 fontSize: (a.fontSize || 14) + 'px',
198 color: isActive ? (a.activeItemColor || '#6366f1') : (isSection ? (a.sectionHeadingColor || '#111827') : (a.itemColor || '#374151')),
199 background: isActive ? (a.activeItemBg || '#ede9fe') : 'transparent',
200 fontWeight: isSection ? String(a.fontWeight||700) : (isActive ? '600' : '400'),
201 cursor: 'pointer',
202 transition: 'background 0.15s, color 0.15s',
203 textDecoration: 'none',
204 borderLeft: isActive ? '3px solid ' + (a.activeIndicatorColor || '#6366f1') : '3px solid transparent',
205 boxSizing: 'border-box'
206 };
207
208 return el(Fragment, { key: idx },
209 item.dividerBefore && a.showDividers && el('div', {
210 style: {
211 height: '1px',
212 background: a.dividerColor || '#e5e7eb',
213 margin: '6px ' + (a.itemPaddingH || 14) + 'px'
214 }
215 }),
216
217 el('a', {
218 href: item.url || '#',
219 style: itemStyle,
220 onClick: function (e) { e.preventDefault(); if (onSetActive) onSetActive(idx); if (isSection && hasChildItems && a.collapsible) toggleSection(idx); }
221 },
222 a.showIcons && item.icon && el('span', { style: { flexShrink: 0, fontSize: '14px' } }, (item.iconType || 'custom-char') !== 'custom-char' ? IP().buildEditorIcon(item.iconType, item.icon, item.iconDashicon, item.iconImageUrl, item.iconDashiconColor) : item.icon),
223 el('span', { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, item.label),
224 a.showBadges && item.badge && el('span', {
225 style: {
226 flexShrink: 0,
227 background: item.badgeColor || (a.badgeBg || '#6366f1'),
228 color: a.badgeColor || '#fff',
229 fontSize: '10px',
230 fontWeight: '700',
231 padding: '1px 7px',
232 borderRadius: '20px',
233 lineHeight: '1.5'
234 }
235 }, item.badge),
236 isSection && hasChildItems && a.collapsible && el('span', {
237 style: {
238 flexShrink: 0,
239 color: a.arrowColor || '#9ca3af',
240 fontSize: '10px',
241 transition: 'transform 0.2s',
242 transform: openSections[idx] ? 'rotate(180deg)' : 'rotate(0deg)',
243 display: 'inline-block'
244 }
245 }, '')
246 )
247 );
248 })
249 )
250 );
251 }
252
253 /* ─── register ─── */
254 var BkbgColorSwatch = function (props) {
255 var _st = useState(false); var isOpen = _st[0]; var setOpen = _st[1];
256 return el(Fragment, {},
257 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' } },
258 el('span', { style: { fontSize: '11px', fontWeight: 500, textTransform: 'uppercase', color: '#1e1e1e' } }, props.label),
259 el('button', {
260 type: 'button',
261 onClick: function () { setOpen(!isOpen); },
262 style: { width: '28px', height: '28px', borderRadius: '4px', border: '1px solid #ccc', background: props.value || '#ffffff', cursor: 'pointer', padding: 0 }
263 })
264 ),
265 isOpen && el(Popover, { onClose: function () { setOpen(false); } },
266 el('div', { style: { padding: '8px' } },
267 el(ColorPicker, { color: props.value, onChangeComplete: function (c) { props.onChange(c.hex); }, enableAlpha: true })
268 )
269 )
270 );
271 };
272
273 registerBlockType('blockenberg/side-menu', {
274 edit: function (props) {
275 var a = props.attributes;
276 var set = props.setAttributes;
277 var itemIdxState = useState(-1);
278 var itemIdx = itemIdxState[0];
279 var setItemIdx = itemIdxState[1];
280
281 var blockProps = useBlockProps((function () {
282 var _tvf = getTV();
283 var s = {
284 paddingTop: (a.paddingTop || 0) + 'px',
285 paddingBottom: (a.paddingBottom || 0) + 'px',
286 background: a.bgColor
287 };
288 if (_tvf) {
289 Object.assign(s, _tvf(a.headingTypo, '--bksmnu-ht-'));
290 Object.assign(s, _tvf(a.itemTypo, '--bksmnu-it-'));
291 }
292 return { style: s };
293 })());
294
295 var inspector = el(InspectorControls, null,
296 /* Items */
297 el(PanelBody, { title: __('Menu Items', 'blockenberg'), initialOpen: true },
298 el(ItemEditor, {
299 items: a.items,
300 onChange: function (v) { set({ items: v }); },
301 activeIdx: itemIdx,
302 setActiveIdx: setItemIdx
303 })
304 ),
305
306 /* Options */
307 el(PanelBody, { title: __('Options', 'blockenberg'), initialOpen: false },
308 el(ToggleControl, { label: __('Show Heading', 'blockenberg'), checked: a.showHeading, onChange: function (v) { set({ showHeading: v }); }, __nextHasNoMarginBottom: true }),
309 a.showHeading && el('div', { style: { marginTop: '8px' } },
310 el(TextControl, { label: __('Heading Text', 'blockenberg'), value: a.headingText, onChange: function (v) { set({ headingText: v }); }, __nextHasNoMarginBottom: true })
311 ),
312 el('div', { style: { marginTop: '8px' } },
313 el(SelectControl, {
314 label: __('Style', 'blockenberg'),
315 value: a.menuStyle,
316 options: [
317 { value: 'default', label: 'Default' },
318 { value: 'pills', label: 'Pills' },
319 { value: 'bordered', label: 'Bordered' },
320 { value: 'minimal', label: 'Minimal' }
321 ],
322 onChange: function (v) { set({ menuStyle: v }); },
323 __nextHasNoMarginBottom: true
324 })
325 ),
326 el(ToggleControl, { label: __('Collapsible Sections', 'blockenberg'), checked: a.collapsible, onChange: function (v) { set({ collapsible: v }); }, __nextHasNoMarginBottom: true }),
327 a.collapsible && el(ToggleControl, { label: __('Default Expanded', 'blockenberg'), checked: a.defaultExpanded, onChange: function (v) { set({ defaultExpanded: v }); }, __nextHasNoMarginBottom: true }),
328 el(ToggleControl, { label: __('Show Icons', 'blockenberg'), checked: a.showIcons, onChange: function (v) { set({ showIcons: v }); }, __nextHasNoMarginBottom: true }),
329 el(ToggleControl, { label: __('Show Badges', 'blockenberg'), checked: a.showBadges, onChange: function (v) { set({ showBadges: v }); }, __nextHasNoMarginBottom: true }),
330 el(ToggleControl, { label: __('Show Dividers', 'blockenberg'), checked: a.showDividers, onChange: function (v) { set({ showDividers: v }); }, __nextHasNoMarginBottom: true })
331 ),
332
333 /* Appearance */
334 el(PanelBody, { title: __('Appearance', 'blockenberg'), initialOpen: false },
335 el(RangeControl, { label: __('Menu Width (px)', 'blockenberg'), value: a.width, min: 160, max: 480, step: 10, onChange: function (v) { set({ width: v }); }, __nextHasNoMarginBottom: true }),
336 el('div', { style: { marginTop: '8px' } },
337 el(RangeControl, { label: __('Item Padding Vertical', 'blockenberg'), value: a.itemPaddingV, min: 4, max: 24, onChange: function (v) { set({ itemPaddingV: v }); }, __nextHasNoMarginBottom: true })
338 ),
339 el('div', { style: { marginTop: '8px' } },
340 el(RangeControl, { label: __('Item Padding Horizontal', 'blockenberg'), value: a.itemPaddingH, min: 8, max: 32, onChange: function (v) { set({ itemPaddingH: v }); }, __nextHasNoMarginBottom: true })
341 ),
342 el('div', { style: { marginTop: '8px' } },
343 el(RangeControl, { label: __('Border Radius', 'blockenberg'), value: a.borderRadius, min: 0, max: 24, onChange: function (v) { set({ borderRadius: v }); }, __nextHasNoMarginBottom: true })
344 ),
345 el('div', { style: { marginTop: '8px' } },
346 el(RangeControl, { label: __('Padding Top (px)', 'blockenberg'), value: a.paddingTop, min: 0, max: 120, step: 4, onChange: function (v) { set({ paddingTop: v }); }, __nextHasNoMarginBottom: true })
347 ),
348 el('div', { style: { marginTop: '8px' } },
349 el(RangeControl, { label: __('Padding Bottom (px)', 'blockenberg'), value: a.paddingBottom, min: 0, max: 120, step: 4, onChange: function (v) { set({ paddingBottom: v }); }, __nextHasNoMarginBottom: true })
350 )
351 ),
352
353 /* Colors */
354
355 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
356 el(getTC(), { label: 'Heading', value: a.headingTypo, onChange: function (v) { set({ headingTypo: v }); } }),
357 el(getTC(), { label: 'Menu Items', value: a.itemTypo, onChange: function (v) { set({ itemTypo: v }); } })
358 ),
359 el(PanelColorSettings, {
360 title: __('Colors', 'blockenberg'),
361 initialOpen: false,
362 colorSettings: [
363 { value: a.bgColor, onChange: function (v) { set({ bgColor: v }); }, label: __('Page Background', 'blockenberg') },
364 { value: a.menuBg, onChange: function (v) { set({ menuBg: v }); }, label: __('Menu Background', 'blockenberg') },
365 { value: a.menuBorder, onChange: function (v) { set({ menuBorder: v }); }, label: __('Menu Border', 'blockenberg') },
366 { value: a.headingColor, onChange: function (v) { set({ headingColor: v }); }, label: __('Heading Text', 'blockenberg') },
367 { value: a.itemColor, onChange: function (v) { set({ itemColor: v }); }, label: __('Item Text', 'blockenberg') },
368 { value: a.itemHoverBg, onChange: function (v) { set({ itemHoverBg: v }); }, label: __('Item Hover Background', 'blockenberg') },
369 { value: a.activeItemBg, onChange: function (v) { set({ activeItemBg: v }); }, label: __('Active Item Background', 'blockenberg') },
370 { value: a.activeItemColor, onChange: function (v) { set({ activeItemColor: v }); }, label: __('Active Item Text', 'blockenberg') },
371 { value: a.activeIndicatorColor, onChange: function (v) { set({ activeIndicatorColor: v }); }, label: __('Active Indicator', 'blockenberg') },
372 { value: a.sectionHeadingColor, onChange: function (v) { set({ sectionHeadingColor: v }); }, label: __('Section Heading', 'blockenberg') },
373 { value: a.dividerColor, onChange: function (v) { set({ dividerColor: v }); }, label: __('Divider', 'blockenberg') },
374 { value: a.badgeBg, onChange: function (v) { set({ badgeBg: v }); }, label: __('Badge Background', 'blockenberg') },
375 { value: a.badgeColor, onChange: function (v) { set({ badgeColor: v }); }, label: __('Badge Text', 'blockenberg') },
376 { value: a.arrowColor, onChange: function (v) { set({ arrowColor: v }); }, label: __('Arrow Icon', 'blockenberg') }
377 ]
378 })
379 );
380
381 return el(Fragment, null,
382 inspector,
383 el('div', blockProps,
384 el('div', { style: { maxWidth: (a.width || 260) + 'px' } },
385 el(MenuPreview, {
386 attributes: a,
387 onSetActive: function (idx) { set({ activeIndex: idx }); }
388 })
389 )
390 )
391 );
392 },
393
394 save: function (props) {
395 return el('div', useBlockProps.save(),
396 el('div', { className: 'bkbg-smnu-app', 'data-opts': JSON.stringify(props.attributes) })
397 );
398 }
399 });
400 }() );
401