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

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

303 lines 21.4 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 _ftbTC, _ftbTV;
7 function _tc() { return (_ftbTC = _ftbTC || window.bkbgTypographyControl); }
8 function _tv() { return (_ftbTV = _ftbTV || window.bkbgTypoCssVars); }
9 var IP = function () { return window.bkbgIconPicker; };
10 var registerBlockType = wp.blocks.registerBlockType;
11 var InspectorControls = wp.blockEditor.InspectorControls;
12 var RichText = wp.blockEditor.RichText;
13 var MediaUpload = wp.blockEditor.MediaUpload;
14 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
15 var useBlockProps = wp.blockEditor.useBlockProps;
16 var PanelBody = wp.components.PanelBody;
17 var Button = wp.components.Button;
18 var ToggleControl = wp.components.ToggleControl;
19 var RangeControl = wp.components.RangeControl;
20 var SelectControl = wp.components.SelectControl;
21 var TextControl = wp.components.TextControl;
22 var TextareaControl = wp.components.TextareaControl;
23 var ColorPicker = wp.components.ColorPicker;
24 var Popover = wp.components.Popover;
25
26 var POSITION_OPTIONS = [
27 { label: __('Tabs left', 'blockenberg'), value: 'left' },
28 { label: __('Tabs right', 'blockenberg'), value: 'right' },
29 { label: __('Tabs top', 'blockenberg'), value: 'top' },
30 ];
31 var ASPECT_OPTIONS = [
32 { label: '16 / 9', value: '16/9' },
33 { label: '4 / 3', value: '4/3' },
34 { label: '1 / 1', value: '1/1' },
35 { label: '3 / 2', value: '3/2' },
36 ];
37 var TAB_STYLE_OPTIONS = [
38 { label: __('Card', 'blockenberg'), value: 'card' },
39 { label: __('Bordered', 'blockenberg'), value: 'bordered' },
40 { label: __('Minimal', 'blockenberg'), value: 'minimal' },
41 ];
42
43 function makeId() { return 'ft' + Math.random().toString(36).substr(2, 5); }
44
45 function buildWrapStyle(a) {
46 return Object.assign({
47 '--bkbg-ftb-accent': a.accentColor,
48 '--bkbg-ftb-active-bg': a.activeTabBg,
49 '--bkbg-ftb-active-color': a.activeTabColor,
50 '--bkbg-ftb-inactive-bg': a.inactiveTabBg,
51 '--bkbg-ftb-inactive-color': a.inactiveTabColor,
52 '--bkbg-ftb-tab-border': a.tabBorderColor,
53 '--bkbg-ftb-headline': a.headlineColor,
54 '--bkbg-ftb-desc': a.descColor,
55 '--bkbg-ftb-placeholder-bg': a.imagePlaceholderBg,
56 '--bkbg-ftb-icon-sz': a.iconSize + 'px',
57 '--bkbg-ftb-tabs-w': a.tabsWidth + 'px',
58 '--bkbg-ftb-gap': a.gap + 'px',
59 '--bkbg-ftb-img-r': a.imageRadius + 'px',
60 paddingTop: a.paddingTop + 'px',
61 paddingBottom: a.paddingBottom + 'px',
62 backgroundColor: a.bgColor || undefined,
63 },
64 _tv()(a.typoLabel, '--bkbg-ftb-tl-'),
65 _tv()(a.typoHeadline, '--bkbg-ftb-th-'),
66 _tv()(a.typoDesc, '--bkbg-ftb-td-')
67 );
68 }
69
70 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
71 var isOpen = openKey === key;
72 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
73 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
74 el('div', { style: { position: 'relative', flexShrink: 0 } },
75 el('button', { type: 'button', title: value || 'default', onClick: function () { setOpenKey(isOpen ? null : key); },
76 style: { width: '28px', height: '28px', borderRadius: '4px', border: isOpen ? '2px solid #007cba' : '2px solid #ddd', cursor: 'pointer', padding: 0, background: value || '#cccccc' }
77 }),
78 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
79 el('div', { style: { padding: '8px' } },
80 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
81 )
82 )
83 )
84 );
85 }
86
87 function ratioPercent(r) {
88 var p = r.split('/');
89 return (parseInt(p[1], 10) / parseInt(p[0], 10) * 100).toFixed(2) + '%';
90 }
91
92 /* ── Single tab button ─────────────────────────────────────────────── */
93 function TabButton(props) {
94 var tab = props.tab;
95 var isActive = props.isActive;
96 var a = props.a;
97 var btnStyle = {
98 display: 'flex', alignItems: 'flex-start', gap: '12px', padding: '16px 18px', cursor: 'pointer',
99 background: isActive ? a.activeTabBg : a.inactiveTabBg, borderRadius: '10px',
100 border: a.tabStyle === 'bordered' ? ('1.5px solid ' + (isActive ? a.accentColor : a.tabBorderColor)) : 'none',
101 borderLeft: a.tabStyle === 'card' && a.tabsPosition !== 'top' && isActive ? ('3px solid ' + a.accentColor) : undefined,
102 marginBottom: a.tabsPosition !== 'top' ? '6px' : '0',
103 marginRight: a.tabsPosition === 'top' ? '6px' : '0',
104 textAlign: 'left', width: a.tabsPosition !== 'top' ? '100%' : 'auto',
105 };
106 return el('button', { type: 'button', style: btnStyle, onClick: props.onClick },
107 el('span', { style: { fontSize: a.iconSize + 'px', flexShrink: 0, lineHeight: 1 } }, (tab.iconType || 'custom-char') !== 'custom-char' && IP() ? IP().buildEditorIcon(el, tab.iconType, tab.icon, tab.iconDashicon, tab.iconImageUrl, tab.iconDashiconColor) : (tab.icon || '')),
108 el('div', null,
109 el('div', { className: 'bkbg-ftb-tab-label', style: { color: isActive ? a.activeTabColor : a.inactiveTabColor, marginBottom: '3px' } }, tab.label),
110 isActive && tab.description && el('div', { className: 'bkbg-ftb-tab-preview-desc', style: { color: a.descColor, maxHeight: '60px', opacity: 1 } }, tab.description.substring(0, 80) + (tab.description.length > 80 ? '' : ''))
111 )
112 );
113 }
114
115 /* ── Image preview pane ────────────────────────────────────────────── */
116 function ImagePane(props) {
117 var tab = props.tab;
118 var a = props.a;
119 var paneStyle = { flex: 1, minWidth: 0, position: 'relative', borderRadius: a.imageRadius + 'px', overflow: 'hidden', boxShadow: a.imageShadow ? '0 20px 60px rgba(0,0,0,0.14)' : 'none', background: a.imagePlaceholderBg };
120 return el('div', { className: 'bkbg-ftb-image-pane', style: paneStyle },
121 el('div', { style: { paddingBottom: ratioPercent(a.imageAspect) } }),
122 tab.imageUrl
123 ? el('img', { src: tab.imageUrl, alt: '', style: { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: a.imageObjectFit } })
124 : el('div', { style: { position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '10px', color: '#9ca3af', fontSize: '13px' } },
125 el('span', { style: { fontSize: '40px' } }, '🖼'),
126 __('Add screenshot in sidebar', 'blockenberg')
127 ),
128 /* headline/desc overlay at bottom for vertical tabs */
129 (a.tabsPosition === 'left' || a.tabsPosition === 'right') && el('div', { style: { position: 'relative', padding: '24px 28px' } },
130 el('h3', { className: 'bkbg-ftb-headline', style: { color: a.headlineColor, margin: '0 0 10px' } }, tab.headline || tab.label),
131 el('p', { className: 'bkbg-ftb-desc', style: { color: a.descColor, margin: 0 } }, tab.description || '')
132 )
133 );
134 }
135
136 registerBlockType('blockenberg/feature-tabs', {
137 title: __('Feature Tabs', 'blockenberg'),
138 icon: 'layout',
139 category: 'bkbg-marketing',
140
141 edit: function (props) {
142 var a = props.attributes;
143 var setAttributes = props.setAttributes;
144
145 var openColorKeyState = useState(null);
146 var openColorKey = openColorKeyState[0];
147 var setOpenColorKey = openColorKeyState[1];
148
149 var activeState = useState(a.defaultActive || 0);
150 var active = activeState[0];
151 var setActive = activeState[1];
152
153 var blockProps = useBlockProps({ style: buildWrapStyle(a) });
154
155 function cc(key, label, attrKey) {
156 return renderColorControl(key, label, a[attrKey], function (val) {
157 var upd = {}; upd[attrKey] = val; setAttributes(upd);
158 }, openColorKey, setOpenColorKey);
159 }
160
161 function updateTab(id, key, val) {
162 setAttributes({ tabs: a.tabs.map(function (t) { if (t.id !== id) return t; var u = Object.assign({}, t); u[key] = val; return u; }) });
163 }
164
165 var safeActive = Math.min(active, Math.max(0, a.tabs.length - 1));
166 var currentTab = a.tabs[safeActive] || {};
167 var isVertical = a.tabsPosition !== 'top';
168 var isRight = a.tabsPosition === 'right';
169
170 var tabList = el('div', { className: 'bkbg-ftb-tab-list', style: {
171 display: 'flex', flexDirection: isVertical ? 'column' : 'row',
172 flexShrink: 0, width: isVertical ? a.tabsWidth + 'px' : '100%',
173 flexWrap: isVertical ? undefined : 'wrap',
174 } },
175 a.tabs.map(function (tab, i) {
176 return el(TabButton, { key: tab.id, tab: tab, isActive: i === safeActive, a: a, onClick: function () { setActive(i); } });
177 })
178 );
179
180 var imagePane = el(ImagePane, { tab: currentTab, a: a });
181
182 var layoutStyle = { display: 'flex', flexDirection: isVertical ? (isRight ? 'row-reverse' : 'row') : 'column', gap: a.gap + 'px', alignItems: 'flex-start' };
183
184 return el(Fragment, null,
185 el(InspectorControls, null,
186 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
187 el(SelectControl, { label: __('Tabs position', 'blockenberg'), value: a.tabsPosition, options: POSITION_OPTIONS, onChange: function (v) { setAttributes({ tabsPosition: v }); } }),
188 el(SelectControl, { label: __('Tab style', 'blockenberg'), value: a.tabStyle, options: TAB_STYLE_OPTIONS, onChange: function (v) { setAttributes({ tabStyle: v }); } }),
189 isVertical && el(RangeControl, { label: __('Tabs sidebar width (px)', 'blockenberg'), value: a.tabsWidth, min: 180, max: 420, onChange: function (v) { setAttributes({ tabsWidth: v }); } }),
190 el(RangeControl, { label: __('Gap between tabs and image (px)', 'blockenberg'), value: a.gap, min: 16, max: 96, onChange: function (v) { setAttributes({ gap: v }); } }),
191 el(SelectControl, { label: __('Image aspect ratio', 'blockenberg'), value: a.imageAspect, options: ASPECT_OPTIONS, onChange: function (v) { setAttributes({ imageAspect: v }); } }),
192 el(RangeControl, { label: __('Image radius (px)', 'blockenberg'), value: a.imageRadius, min: 0, max: 32, onChange: function (v) { setAttributes({ imageRadius: v }); } }),
193 el(ToggleControl, { label: __('Image shadow', 'blockenberg'), checked: a.imageShadow, onChange: function (v) { setAttributes({ imageShadow: v }); }, __nextHasNoMarginBottom: true }),
194 el(ToggleControl, { label: __('Animate on scroll', 'blockenberg'), checked: a.animate, onChange: function (v) { setAttributes({ animate: v }); }, __nextHasNoMarginBottom: true })
195 ),
196 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
197 _tc() && el(_tc(), { label: __('Tab Label', 'blockenberg'), value: a.typoLabel, onChange: function (v) { setAttributes({ typoLabel: v }); } }),
198 _tc() && el(_tc(), { label: __('Headline', 'blockenberg'), value: a.typoHeadline, onChange: function (v) { setAttributes({ typoHeadline: v }); } }),
199 _tc() && el(_tc(), { label: __('Description', 'blockenberg'), value: a.typoDesc, onChange: function (v) { setAttributes({ typoDesc: v }); } }),
200 el(RangeControl, { label: __('Icon size (px)', 'blockenberg'), value: a.iconSize, min: 16, max: 48, onChange: function (v) { setAttributes({ iconSize: v }); } })
201 ),
202 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
203 cc('accentColor', __('Accent / active indicator', 'blockenberg'), 'accentColor'),
204 cc('activeTabBg', __('Active tab background', 'blockenberg'), 'activeTabBg'),
205 cc('activeTabColor', __('Active tab text', 'blockenberg'), 'activeTabColor'),
206 cc('inactiveTabBg', __('Inactive tab background', 'blockenberg'), 'inactiveTabBg'),
207 cc('inactiveTabColor', __('Inactive tab text', 'blockenberg'), 'inactiveTabColor'),
208 cc('tabBorderColor', __('Tab border', 'blockenberg'), 'tabBorderColor'),
209 cc('headlineColor', __('Content headline', 'blockenberg'), 'headlineColor'),
210 cc('descColor', __('Content description', 'blockenberg'), 'descColor'),
211 cc('imagePlaceholderBg',__('Image placeholder bg', 'blockenberg'), 'imagePlaceholderBg'),
212 cc('bgColor', __('Section background', 'blockenberg'), 'bgColor')
213 ),
214 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
215 el(RangeControl, { label: __('Padding top (px)', 'blockenberg'), value: a.paddingTop, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingTop: v }); } }),
216 el(RangeControl, { label: __('Padding bottom (px)', 'blockenberg'), value: a.paddingBottom, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingBottom: v }); } })
217 ),
218 el(PanelBody, { title: __('Tabs', 'blockenberg'), initialOpen: false },
219 a.tabs.map(function (tab, ti) {
220 return el(PanelBody, { key: tab.id, title: (tab.icon || '') + ' ' + (tab.label || 'Tab ' + (ti + 1)), initialOpen: ti === safeActive },
221 el(IP().IconPickerControl, {
222 iconType: tab.iconType || 'custom-char',
223 customChar: tab.icon || '',
224 dashicon: tab.iconDashicon || '',
225 imageUrl: tab.iconImageUrl || '',
226 onChangeType: function (v) { updateTab(tab.id, 'iconType', v); },
227 onChangeChar: function (v) { updateTab(tab.id, 'icon', v); },
228 onChangeDashicon: function (v) { updateTab(tab.id, 'iconDashicon', v); },
229 onChangeImageUrl: function (v) { updateTab(tab.id, 'iconImageUrl', v); },
230 label: __('Icon', 'blockenberg')
231 }),
232 el(TextControl, { label: __('Tab label', 'blockenberg'), value: tab.label, onChange: function (v) { updateTab(tab.id, 'label', v); } }),
233 el(TextControl, { label: __('Headline', 'blockenberg'), value: tab.headline, onChange: function (v) { updateTab(tab.id, 'headline', v); } }),
234 el(TextareaControl, { label: __('Description', 'blockenberg'), value: tab.description, onChange: function (v) { updateTab(tab.id, 'description', v); }, rows: 3 }),
235 el(MediaUploadCheck, null,
236 el(MediaUpload, {
237 onSelect: function (m) { updateTab(tab.id, 'imageUrl', m.url); updateTab(tab.id, 'imageId', m.id); },
238 allowedTypes: ['image'], value: tab.imageId,
239 render: function (p) { return el('div', null,
240 tab.imageUrl && el('img', { src: tab.imageUrl, style: { width: '100%', borderRadius: '6px', marginBottom: '6px' } }),
241 el(Button, { onClick: p.open, variant: tab.imageUrl ? 'secondary' : 'primary', size: 'compact' }, tab.imageUrl ? __('Replace screenshot', 'blockenberg') : __('Add screenshot', 'blockenberg'))
242 ); }
243 })
244 ),
245 el(Button, { onClick: function () { setAttributes({ tabs: a.tabs.filter(function (t) { return t.id !== tab.id; }) }); if (safeActive >= ti) setActive(Math.max(0, safeActive - 1)); }, isDestructive: true, variant: 'tertiary', size: 'compact' }, __('Remove tab', 'blockenberg'))
246 );
247 }),
248 el(Button, { variant: 'primary', onClick: function () { setAttributes({ tabs: a.tabs.concat([{ id: makeId(), icon: '🔧', iconType: 'custom-char', iconDashicon: '', iconImageUrl: '', label: __('New Feature', 'blockenberg'), headline: '', description: '', imageUrl: '', imageId: 0 }]) }); setActive(a.tabs.length); }, style: { marginTop: '8px' } }, __('+ Add Tab', 'blockenberg'))
249 )
250 ),
251
252 el('div', blockProps,
253 el('div', { className: 'bkbg-ftb-layout bkbg-ftb--' + a.tabsPosition, style: layoutStyle },
254 isRight ? imagePane : tabList,
255 isRight ? tabList : imagePane
256 )
257 )
258 );
259 },
260
261 save: function (props) {
262 var a = props.attributes;
263 var isVertical = a.tabsPosition !== 'top';
264 var isRight = a.tabsPosition === 'right';
265 var layoutStyle = { display: 'flex', flexDirection: isVertical ? (isRight ? 'row-reverse' : 'row') : 'column', gap: a.gap + 'px', alignItems: 'flex-start' };
266
267 var tabList = el('nav', { className: 'bkbg-ftb-tab-list', role: 'tablist', style: { flexShrink: 0, width: isVertical ? a.tabsWidth + 'px' : '100%', display: 'flex', flexDirection: isVertical ? 'column' : 'row', flexWrap: 'wrap' } },
268 a.tabs.map(function (tab, i) {
269 return el('button', { key: tab.id, type: 'button', role: 'tab', className: 'bkbg-ftb-tab-btn' + (i === (a.defaultActive || 0) ? ' is-active' : ''), 'data-tab': tab.id, 'aria-selected': i === (a.defaultActive || 0) ? 'true' : 'false' },
270 el('span', { className: 'bkbg-ftb-tab-icon', 'aria-hidden': 'true' }, (tab.iconType || 'custom-char') !== 'custom-char' && IP() ? IP().buildSaveIcon(el, tab.iconType, tab.icon, tab.iconDashicon, tab.iconImageUrl, tab.iconDashiconColor) : tab.icon),
271 el('div', { className: 'bkbg-ftb-tab-text' },
272 el('span', { className: 'bkbg-ftb-tab-label' }, tab.label),
273 el('span', { className: 'bkbg-ftb-tab-preview-desc' }, tab.description)
274 )
275 );
276 })
277 );
278
279 var imagePane = el('div', { className: 'bkbg-ftb-panels', style: { flex: 1, minWidth: 0, position: 'relative' } },
280 a.tabs.map(function (tab, i) {
281 return el('div', { key: tab.id, className: 'bkbg-ftb-panel' + (i === (a.defaultActive || 0) ? ' is-active' : ''), role: 'tabpanel', 'data-panel': tab.id, 'aria-hidden': i === (a.defaultActive || 0) ? 'false' : 'true' },
282 el('div', { className: 'bkbg-ftb-image-pane', style: { position: 'relative', borderRadius: a.imageRadius + 'px', overflow: 'hidden', boxShadow: a.imageShadow ? '0 20px 60px rgba(0,0,0,0.14)' : 'none' } },
283 el('div', { style: { paddingBottom: (a.imageAspect.split('/').reduce(function (_, v, idx, arr) { return idx === 1 ? (parseInt(v,10) / parseInt(arr[0],10) * 100).toFixed(2) : _; }, '56.25') + '%') } }),
284 tab.imageUrl && el('img', { src: tab.imageUrl, alt: tab.headline || tab.label || '', loading: 'lazy', style: { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: a.imageObjectFit } })
285 ),
286 isVertical && el('div', { className: 'bkbg-ftb-panel-content' },
287 el('h3', { className: 'bkbg-ftb-headline' }, tab.headline || tab.label),
288 el('p', { className: 'bkbg-ftb-desc' }, tab.description)
289 )
290 );
291 })
292 );
293
294 return el('div', wp.blockEditor.useBlockProps.save({ className: 'bkbg-ftb-wrapper', style: buildWrapStyle(a), 'data-animate': a.animate ? '1' : undefined }),
295 el('div', { className: 'bkbg-ftb-layout bkbg-ftb--' + a.tabsPosition, style: layoutStyle },
296 isRight ? imagePane : tabList,
297 isRight ? tabList : imagePane
298 )
299 );
300 }
301 });
302 }() );
303