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

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

310 lines 19.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 MediaUpload = wp.blockEditor.MediaUpload;
9 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
10 var useBlockProps = wp.blockEditor.useBlockProps;
11 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
12 var PanelBody = wp.components.PanelBody;
13 var ToggleControl = wp.components.ToggleControl;
14 var RangeControl = wp.components.RangeControl;
15 var SelectControl = wp.components.SelectControl;
16 var TextControl = wp.components.TextControl;
17 var Button = wp.components.Button;
18
19 var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
20 var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
21
22 var SHADOW_OPTIONS = [
23 { label: 'None', value: 'none' },
24 { label: 'Small', value: 'sm' },
25 { label: 'Medium', value: 'md' },
26 { label: 'Large', value: 'lg' },
27 ];
28
29 function star(filled, color) {
30 return el('span', { style: { color: filled ? color : '#e5e7eb', fontSize: 14, lineHeight: 1 } }, '�
31 ');
32 }
33
34 function updateItem(items, idx, field, val) {
35 return items.map(function (it, i) {
36 if (i !== idx) return it;
37 var p = {}; p[field] = val;
38 return Object.assign({}, it, p);
39 });
40 }
41
42 function AvatarInitial(name, size) {
43 var initials = (name || 'A').split(' ').map(function (w) { return w[0]; }).slice(0, 2).join('').toUpperCase();
44 var colors = ['#6366f1', '#8b5cf6', '#ec4899', '#14b8a6', '#f59e0b', '#ef4444'];
45 var hash = name.split('').reduce(function (acc, c) { return acc + c.charCodeAt(0); }, 0);
46 var bg = colors[hash % colors.length];
47 return el('div', {
48 style: {
49 width: size + 'px', height: size + 'px', borderRadius: '50%',
50 background: bg, color: '#fff', display: 'flex',
51 alignItems: 'center', justifyContent: 'center',
52 fontWeight: 700, fontSize: Math.round(size * 0.38) + 'px',
53 flexShrink: 0,
54 }
55 }, initials);
56 }
57
58 function renderCard(item, a) {
59 var shadow = a.cardShadow === 'sm' ? '0 1px 6px rgba(0,0,0,0.07)'
60 : a.cardShadow === 'md' ? '0 4px 16px rgba(0,0,0,0.10)'
61 : a.cardShadow === 'lg' ? '0 8px 32px rgba(0,0,0,0.13)' : 'none';
62 return el('div', {
63 style: {
64 background: a.cardBg,
65 border: '1px solid ' + a.cardBorder,
66 borderRadius: a.cardRadius + 'px',
67 padding: a.cardPadding + 'px',
68 width: a.cardWidth + 'px',
69 flexShrink: 0,
70 boxShadow: shadow,
71 display: 'flex',
72 flexDirection: 'column',
73 gap: 14,
74 position: 'relative',
75 overflow: 'hidden',
76 }
77 },
78 // Big quote mark
79 el('div', {
80 style: {
81 position: 'absolute', top: 14, right: 16,
82 fontSize: 56, lineHeight: 1, fontFamily: 'Georgia, serif',
83 color: a.quoteIconColor, pointerEvents: 'none', userSelect: 'none',
84 }
85 }, '\u201C'),
86 // Stars
87 a.showStars && el('div', { style: { display: 'flex', gap: 2 } },
88 [1, 2, 3, 4, 5].map(function (n) { return star(n <= item.rating, a.starColor); })
89 ),
90 // Quote
91 el('p', {
92 className: 'bkbg-tm-quote',
93 style: {
94 margin: 0, color: a.quoteColor,
95 flexGrow: 1,
96 }
97 }, item.quote),
98 // Author row
99 el('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginTop: 4 } },
100 a.showAvatar && (
101 item.avatarUrl
102 ? el('img', { src: item.avatarUrl, alt: item.author, style: { width: a.avatarSize + 'px', height: a.avatarSize + 'px', borderRadius: '50%', objectFit: 'cover', flexShrink: 0 } })
103 : AvatarInitial(item.author, a.avatarSize)
104 ),
105 el('div', null,
106 el('div', { className: 'bkbg-tm-author-name', style: { color: a.authorColor } }, item.author),
107 (a.showRole || a.showCompany) && el('div', { className: 'bkbg-tm-author-meta', style: { color: a.roleColor } },
108 [a.showRole ? item.role : '', a.showCompany ? item.company : ''].filter(Boolean).join(', ')
109 )
110 )
111 )
112 );
113 }
114
115 registerBlockType('blockenberg/testimonial-marquee', {
116 title: __('Testimonial Marquee', 'blockenberg'),
117 icon: 'format-quote',
118 category: 'bkbg-marketing',
119 description: __('Infinite auto-scrolling testimonial ticker with ratings, avatars, and author details.', 'blockenberg'),
120
121 edit: function (props) {
122 var a = props.attributes;
123 var set = props.setAttributes;
124 var activeSt = useState(0);
125 var activeIdx = activeSt[0];
126 var setActiveIdx = activeSt[1];
127 var blockProps = useBlockProps((function () {
128 var _tvf = getTypoCssVars();
129 var s = { background: a.wrapperBg || '', padding: a.paddingY + 'px 0', overflow: 'hidden' };
130 if (_tvf) { Object.assign(s, _tvf(a.quoteTypo, '--bkmq-qt-'), _tvf(a.nameTypo, '--bkmq-nm-'), _tvf(a.roleTypo, '--bkmq-rl-')); }
131 return { style: s };
132 })());
133
134 function addItem() {
135 set({ items: a.items.concat([{ quote: 'A great testimonial goes here.', author: 'Happy Customer', role: 'Customer', company: '', avatarUrl: '', avatarId: 0, rating: 5, companyLogo: '', companyLogoId: 0 }]) });
136 }
137 function removeItem(i) {
138 if (a.items.length <= 1) return;
139 set({ items: a.items.filter(function (_, idx) { return idx !== i; }) });
140 setActiveIdx(Math.max(0, activeIdx - 1));
141 }
142
143 return el(Fragment, null,
144 el(InspectorControls, null,
145 // Testimonials
146 el(PanelBody, { title: 'Testimonials (' + a.items.length + ')', initialOpen: true },
147 a.items.map(function (item, i) {
148 return el('div', {
149 key: i,
150 style: {
151 border: i === activeIdx ? '2px solid #6366f1' : '1px solid #e5e7eb',
152 borderRadius: 8, padding: 10, marginBottom: 8,
153 background: i === activeIdx ? '#f5f3ff' : '#fff',
154 cursor: 'pointer',
155 }
156 },
157 el('div', {
158 style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
159 onClick: function () { setActiveIdx(i); }
160 },
161 el('span', { style: { fontWeight: 600, fontSize: 12 } }, '�
162 '.repeat(item.rating) + ' ' + item.author),
163 el(Button, { isDestructive: true, variant: 'tertiary', size: 'small', onClick: function (e) { e.stopPropagation(); removeItem(i); }, __nextHasNoMarginBottom: true }, '')
164 ),
165 i === activeIdx && el(Fragment, null,
166 el('div', { style: { height: 8 } }),
167 el(TextControl, { label: 'Quote', value: item.quote, onChange: function (v) { set({ items: updateItem(a.items, i, 'quote', v) }); }, __nextHasNoMarginBottom: true }),
168 el('div', { style: { height: 6 } }),
169 el(TextControl, { label: 'Author name', value: item.author, onChange: function (v) { set({ items: updateItem(a.items, i, 'author', v) }); }, __nextHasNoMarginBottom: true }),
170 el('div', { style: { height: 6 } }),
171 el(TextControl, { label: 'Role / Title', value: item.role, onChange: function (v) { set({ items: updateItem(a.items, i, 'role', v) }); }, __nextHasNoMarginBottom: true }),
172 el('div', { style: { height: 6 } }),
173 el(TextControl, { label: 'Company', value: item.company, onChange: function (v) { set({ items: updateItem(a.items, i, 'company', v) }); }, __nextHasNoMarginBottom: true }),
174 el('div', { style: { height: 6 } }),
175 el(RangeControl, { label: 'Star rating', value: item.rating, min: 1, max: 5, onChange: function (v) { set({ items: updateItem(a.items, i, 'rating', v) }); }, __nextHasNoMarginBottom: true }),
176 el('div', { style: { height: 6 } }),
177 el('div', { style: { fontSize: 11, fontWeight: 600, textTransform: 'uppercase', color: '#6b7280', marginBottom: 4 } }, 'Avatar'),
178 el(MediaUploadCheck, null,
179 el(MediaUpload, {
180 onSelect: function (media) {
181 var updated = updateItem(a.items, i, 'avatarUrl', media.url);
182 updated = updateItem(updated, i, 'avatarId', media.id);
183 set({ items: updated });
184 },
185 allowedTypes: ['image'],
186 value: item.avatarId,
187 render: function (ref) {
188 return el(Button, {
189 onClick: ref.open,
190 variant: item.avatarUrl ? 'secondary' : 'primary',
191 size: 'small',
192 __nextHasNoMarginBottom: true,
193 }, item.avatarUrl ? 'Change Avatar' : 'Add Avatar');
194 }
195 })
196 ),
197 item.avatarUrl && el(Button, {
198 onClick: function () { set({ items: updateItem(a.items, i, 'avatarUrl', '') }); },
199 isDestructive: true, variant: 'link', size: 'small',
200 style: { display: 'block', marginTop: 4 },
201 __nextHasNoMarginBottom: true,
202 }, 'Remove Avatar')
203 )
204 );
205 }),
206 el(Button, { variant: 'secondary', onClick: addItem, style: { width: '100%', justifyContent: 'center', marginTop: 4 }, __nextHasNoMarginBottom: true }, '+ Add Testimonial')
207 ),
208
209 // Display
210 el(PanelBody, { title: 'Display Options', initialOpen: false },
211 el(RangeControl, { label: 'Rows', value: a.rows, min: 1, max: 2, onChange: function (v) { set({ rows: v }); }, __nextHasNoMarginBottom: true }),
212 el('div', { style: { height: 10 } }),
213 el(SelectControl, { label: 'Direction', value: a.direction, options: [{ label: 'Left (→)', value: 'left' }, { label: 'Right (←)', value: 'right' }], onChange: function (v) { set({ direction: v }); }, __nextHasNoMarginBottom: true }),
214 el('div', { style: { height: 10 } }),
215 el(RangeControl, { label: 'Speed (higher = faster)', value: a.speed, min: 10, max: 200, onChange: function (v) { set({ speed: v }); }, __nextHasNoMarginBottom: true }),
216 el('div', { style: { height: 10 } }),
217 el(ToggleControl, { label: 'Pause on hover', checked: a.pauseOnHover, onChange: function (v) { set({ pauseOnHover: v }); }, __nextHasNoMarginBottom: true }),
218 el('div', { style: { height: 8 } }),
219 el(ToggleControl, { label: 'Fade edges', checked: a.fadeEdges, onChange: function (v) { set({ fadeEdges: v }); }, __nextHasNoMarginBottom: true }),
220 el('div', { style: { height: 8 } }),
221 el(ToggleControl, { label: 'Show stars', checked: a.showStars, onChange: function (v) { set({ showStars: v }); }, __nextHasNoMarginBottom: true }),
222 el('div', { style: { height: 8 } }),
223 el(ToggleControl, { label: 'Show avatar', checked: a.showAvatar, onChange: function (v) { set({ showAvatar: v }); }, __nextHasNoMarginBottom: true }),
224 el('div', { style: { height: 8 } }),
225 el(ToggleControl, { label: 'Show role', checked: a.showRole, onChange: function (v) { set({ showRole: v }); }, __nextHasNoMarginBottom: true }),
226 el('div', { style: { height: 8 } }),
227 el(ToggleControl, { label: 'Show company', checked: a.showCompany, onChange: function (v) { set({ showCompany: v }); }, __nextHasNoMarginBottom: true })
228 ),
229
230 // Card dimensions
231 el(PanelBody, { title: 'Card Style', initialOpen: false },
232 el(RangeControl, { label: 'Card width (px)', value: a.cardWidth, min: 200, max: 600, onChange: function (v) { set({ cardWidth: v }); }, __nextHasNoMarginBottom: true }),
233 el('div', { style: { height: 10 } }),
234 el(RangeControl, { label: 'Card gap (px)', value: a.cardGap, min: 8, max: 60, onChange: function (v) { set({ cardGap: v }); }, __nextHasNoMarginBottom: true }),
235 el('div', { style: { height: 10 } }),
236 el(RangeControl, { label: 'Card radius (px)', value: a.cardRadius, min: 0, max: 32, onChange: function (v) { set({ cardRadius: v }); }, __nextHasNoMarginBottom: true }),
237 el('div', { style: { height: 10 } }),
238 el(RangeControl, { label: 'Card padding (px)', value: a.cardPadding, min: 12, max: 48, onChange: function (v) { set({ cardPadding: v }); }, __nextHasNoMarginBottom: true }),
239 el('div', { style: { height: 10 } }),
240 el('div', { style: { height: 10 } }),
241 el(RangeControl, { label: 'Avatar size (px)', value: a.avatarSize, min: 28, max: 72, onChange: function (v) { set({ avatarSize: v }); }, __nextHasNoMarginBottom: true }),
242 el('div', { style: { height: 10 } }),
243 el(RangeControl, { label: 'Vertical padding (px)', value: a.paddingY, min: 0, max: 100, onChange: function (v) { set({ paddingY: v }); }, __nextHasNoMarginBottom: true }),
244 el('div', { style: { height: 10 } }),
245 el(SelectControl, { label: 'Card shadow', value: a.cardShadow, options: SHADOW_OPTIONS, onChange: function (v) { set({ cardShadow: v }); }, __nextHasNoMarginBottom: true })
246 ),
247
248 // Colors
249
250 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
251 getTypoControl() && getTypoControl()({ label: __('Quote', 'blockenberg'), value: a.quoteTypo, onChange: function (v) { set({ quoteTypo: v }); } }),
252 getTypoControl() && getTypoControl()({ label: __('Author Name', 'blockenberg'), value: a.nameTypo, onChange: function (v) { set({ nameTypo: v }); } }),
253 getTypoControl() && getTypoControl()({ label: __('Role / Company', 'blockenberg'), value: a.roleTypo, onChange: function (v) { set({ roleTypo: v }); } })
254 ),
255 el(PanelColorSettings, {
256 title: 'Colors',
257 initialOpen: false,
258 colorSettings: [
259 { label: 'Wrapper Background', value: a.wrapperBg, onChange: function (v) { set({ wrapperBg: v || '' }); } },
260 { label: 'Card Background', value: a.cardBg, onChange: function (v) { set({ cardBg: v || '#ffffff' }); } },
261 { label: 'Card Border', value: a.cardBorder, onChange: function (v) { set({ cardBorder: v || '#e5e7eb' }); } },
262 { label: 'Quote Text', value: a.quoteColor, onChange: function (v) { set({ quoteColor: v || '#374151' }); } },
263 { label: 'Star Color', value: a.starColor, onChange: function (v) { set({ starColor: v || '#f59e0b' }); } },
264 { label: 'Author Name', value: a.authorColor, onChange: function (v) { set({ authorColor: v || '#111827' }); } },
265 { label: 'Role / Company', value: a.roleColor, onChange: function (v) { set({ roleColor: v || '#6b7280' }); } },
266 { label: 'Quote Icon Color', value: a.quoteIconColor, onChange: function (v) { set({ quoteIconColor: v || '#e0e7ff' }); } },
267 ]
268 })
269 ),
270
271 // ── Editor Preview ──
272 el('div', blockProps,
273 el('div', {
274 style: {
275 display: a.rows === 2 ? 'flex' : 'block',
276 flexDirection: 'column',
277 gap: a.rows === 2 ? a.cardGap + 'px' : 0,
278 overflow: 'hidden',
279 }
280 },
281 // Row 1
282 el('div', { style: { display: 'flex', gap: a.cardGap + 'px', overflowX: 'auto', paddingBottom: 8 } },
283 a.items.map(function (item, i) { return el(Fragment, { key: i }, renderCard(item, a)); })
284 ),
285 // Row 2 (reversed order preview)
286 a.rows === 2 && el('div', { style: { display: 'flex', gap: a.cardGap + 'px', overflowX: 'auto' } },
287 a.items.slice().reverse().map(function (item, i) { return el(Fragment, { key: i }, renderCard(item, a)); })
288 )
289 ),
290 el('p', { style: { textAlign: 'center', fontSize: 11, color: '#9ca3af', margin: '10px 0 0' } },
291 '🎞 Infinite scroll active on frontend · ' + a.items.length + ' testimonials · ' + a.rows + ' row(s)'
292 )
293 )
294 );
295 },
296
297 save: function (props) {
298 var _tvf = getTypoCssVars();
299 var a = props.attributes;
300 return el('div', (function () {
301 var s = {};
302 if (_tvf) { Object.assign(s, _tvf(a.quoteTypo, '--bkmq-qt-'), _tvf(a.nameTypo, '--bkmq-nm-'), _tvf(a.roleTypo, '--bkmq-rl-')); }
303 return useBlockProps.save({ style: s });
304 })(),
305 el('div', { className: 'bkbg-tm-app', 'data-opts': JSON.stringify(a) })
306 );
307 }
308 });
309 }() );
310