PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.11
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.11
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 / interactive-poll / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.11, at blocks/interactive-poll/index.js

434 lines 21.2 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 useState = wp.element.useState;
4 var Fragment = wp.element.Fragment;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var __ = wp.i18n.__;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var TextControl = wp.components.TextControl;
12 var TextareaControl = wp.components.TextareaControl;
13 var ToggleControl = wp.components.ToggleControl;
14 var RangeControl = wp.components.RangeControl;
15 var Button = wp.components.Button;
16 var Notice = wp.components.Notice;
17
18 var _TypographyControl, _typoCssVars;
19 function getTypographyControl() { return _TypographyControl || (_TypographyControl = window.bkbgTypographyControl); }
20 function getTypoCssVars() { return _typoCssVars || (_typoCssVars = window.bkbgTypoCssVars); }
21
22 // ── Preview of a single option as a vote button ──────────────────────────
23 function OptionButton(props) {
24 var text = props.text;
25 var bg = props.buttonBg;
26 var color = props.buttonColor;
27 var radius = props.borderRadius;
28 return el('button', {
29 className: 'bkip-option-btn',
30 style: {
31 background: bg,
32 color: color,
33 borderRadius: radius + 'px',
34 border: 'none',
35 padding: '10px 20px',
36 cursor: 'pointer',
37 fontWeight: 600,
38 fontSize: '15px',
39 width: '100%',
40 marginBottom: '8px',
41 textAlign: 'left',
42 }
43 }, text || __('Option', 'blockenberg'));
44 }
45
46 // ── Preview of results bar ────────────────────────────────────────────────
47 function ResultBar(props) {
48 var text = props.text;
49 var pct = props.pct;
50 var barColor = props.barColor;
51 var barBg = props.barBg;
52 var radius = props.borderRadius;
53 var textColor = props.resultTextColor;
54 return el('div', { className: 'bkip-result', style: { marginBottom: '14px' } },
55 el('div', {
56 style: {
57 display: 'flex',
58 justifyContent: 'space-between',
59 marginBottom: '4px',
60 fontSize: '14px',
61 color: textColor || undefined,
62 }
63 },
64 el('span', null, text),
65 el('span', null, pct + '%')
66 ),
67 el('div', {
68 className: 'bkip-bar',
69 style: {
70 background: barBg,
71 borderRadius: radius + 'px',
72 overflow: 'hidden',
73 height: '10px',
74 }
75 },
76 el('div', {
77 className: 'bkip-bar-fill',
78 style: {
79 width: pct + '%',
80 background: barColor,
81 height: '100%',
82 borderRadius: radius + 'px',
83 transition: 'width 0.6s ease',
84 }
85 })
86 )
87 );
88 }
89
90 // ── Editor preview ────────────────────────────────────────────────────────
91 function PollPreview(props) {
92 var attrs = props.attributes;
93 var options = attrs.options || [];
94 var previewMode = useState('vote');
95 var mode = previewMode[0];
96 var setMode = previewMode[1];
97
98 var totalSample = options.reduce(function (sum, o) { return sum + (o.votes || 0); }, 0);
99
100 return el('div', {
101 className: 'bkip-wrap',
102 style: {
103 background: attrs.wrapBg || undefined,
104 border: attrs.wrapBorder ? '1px solid ' + attrs.wrapBorder : undefined,
105 borderRadius: attrs.borderRadius + 'px',
106 padding: attrs.wrapPadding + 'px',
107 maxWidth: attrs.maxWidth + 'px',
108 }
109 },
110 /* Editor toggle */
111 el('div', { style: { display: 'flex', gap: '8px', marginBottom: '16px' } },
112 el(Button, {
113 variant: mode === 'vote' ? 'primary' : 'secondary',
114 size: 'small',
115 onClick: function () { setMode('vote'); }
116 }, __('Vote view', 'blockenberg')),
117 el(Button, {
118 variant: mode === 'results' ? 'primary' : 'secondary',
119 size: 'small',
120 onClick: function () { setMode('results'); }
121 }, __('Results view', 'blockenberg'))
122 ),
123 /* Question */
124 el('p', {
125 className: 'bkip-question',
126 style: {
127 marginBottom: '20px',
128 color: attrs.questionColor || undefined,
129 }
130 }, attrs.question || __('Your question here…', 'blockenberg')),
131 /* Vote mode */
132 mode === 'vote' && el(Fragment, null,
133 options.map(function (opt, i) {
134 return el(OptionButton, {
135 key: i,
136 text: opt.text,
137 buttonBg: attrs.buttonBg,
138 buttonColor: attrs.buttonColor,
139 borderRadius: attrs.borderRadius,
140 });
141 }),
142 options.length === 0 && el(Notice, { status: 'warning', isDismissible: false },
143 __('Add options in the sidebar.', 'blockenberg')
144 )
145 ),
146 /* Results mode */
147 mode === 'results' && el(Fragment, null,
148 options.map(function (opt, i) {
149 var pct = totalSample > 0 ? Math.round((opt.votes / totalSample) * 100) : 0;
150 return el(ResultBar, {
151 key: i,
152 text: opt.text,
153 pct: pct,
154 barColor: attrs.barColor,
155 barBg: attrs.barBg,
156 borderRadius: attrs.borderRadius,
157 resultTextColor: attrs.resultTextColor,
158 });
159 }),
160 attrs.totalVotesLabel && el('p', {
161 style: { fontSize: '13px', color: '#888', marginTop: '8px' }
162 }, totalSample + ' ' + __('votes', 'blockenberg'))
163 )
164 );
165 }
166
167 // ── Main block ────────────────────────────────────────────────────────────
168 registerBlockType('blockenberg/interactive-poll', {
169 title: __('Interactive Poll', 'blockenberg'),
170 description: __('Clickable voting poll with animated results.', 'blockenberg'),
171 category: 'bkbg-interactive',
172 icon: 'chart-bar',
173
174 edit: function (props) {
175 var attributes = props.attributes;
176 var setAttributes = props.setAttributes;
177 var options = attributes.options || [];
178
179 // Generate instance id once
180 if (!attributes.instanceId) {
181 setAttributes({ instanceId: 'bkip-' + Math.random().toString(36).slice(2, 8) });
182 }
183
184 function setOption(index, key, value) {
185 var updated = options.map(function (o, i) {
186 if (i === index) {
187 var copy = Object.assign({}, o);
188 copy[key] = value;
189 return copy;
190 }
191 return o;
192 });
193 setAttributes({ options: updated });
194 }
195
196 function addOption() {
197 setAttributes({ options: options.concat([{ text: 'New option', votes: 0 }]) });
198 }
199
200 function removeOption(index) {
201 setAttributes({ options: options.filter(function (_, i) { return i !== index; }) });
202 }
203
204 var blockProps = useBlockProps((function () {
205 var s = {};
206 var _tv = getTypoCssVars();
207 Object.assign(s, _tv(attributes.questionTypo, '--bkip-q-'));
208 return { className: 'bkip-editor-wrap', style: s };
209 })());
210
211 return el(Fragment, null,
212 el(InspectorControls, null,
213 /* ── Question ─────────────────────────────────────────── */
214 el(PanelBody, { title: __('Question', 'blockenberg'), initialOpen: true },
215 el(TextareaControl, {
216 label: __('Question text', 'blockenberg'),
217 value: attributes.question,
218 onChange: function (v) { setAttributes({ question: v }); },
219 rows: 3,
220 }),
221 el(ToggleControl, {
222 label: __('Show total votes', 'blockenberg'),
223 checked: attributes.totalVotesLabel,
224 onChange: function (v) { setAttributes({ totalVotesLabel: v }); },
225 __nextHasNoMarginBottom: true,
226 })
227 ),
228 /* ── Options ──────────────────────────────────────────── */
229 el(PanelBody, { title: __('Poll Options', 'blockenberg'), initialOpen: true },
230 options.map(function (opt, i) {
231 return el(PanelBody, {
232 key: i,
233 title: (i + 1) + '. ' + (opt.text || __('Option', 'blockenberg')),
234 initialOpen: false,
235 },
236 el(TextControl, {
237 label: __('Option text', 'blockenberg'),
238 value: opt.text,
239 onChange: function (v) { setOption(i, 'text', v); },
240 }),
241 el(RangeControl, {
242 label: __('Initial votes (for preview)', 'blockenberg'),
243 value: opt.votes || 0,
244 min: 0, max: 999,
245 onChange: function (v) { setOption(i, 'votes', v); },
246 }),
247 options.length > 2 && el(Button, {
248 variant: 'link',
249 isDestructive: true,
250 onClick: function () { removeOption(i); }
251 }, __('Remove option', 'blockenberg'))
252 );
253 }),
254 options.length < 8 && el(Button, {
255 variant: 'secondary',
256 onClick: addOption,
257 style: { marginTop: '8px', width: '100%' },
258 }, __('+ Add option', 'blockenberg'))
259 ),
260 /* ── Behaviour ────────────────────────────────────────── */
261 el(PanelBody, { title: __('Behaviour', 'blockenberg'), initialOpen: false },
262 el(ToggleControl, {
263 label: __('Allow revoting', 'blockenberg'),
264 checked: attributes.allowRevote,
265 onChange: function (v) { setAttributes({ allowRevote: v }); },
266 __nextHasNoMarginBottom: true,
267 }),
268 el(ToggleControl, {
269 label: __('Show results before voting', 'blockenberg'),
270 checked: attributes.showResultsBefore,
271 onChange: function (v) { setAttributes({ showResultsBefore: v }); },
272 __nextHasNoMarginBottom: true,
273 }),
274 el(ToggleControl, {
275 label: __('Animate result bars', 'blockenberg'),
276 checked: attributes.animateBars,
277 onChange: function (v) { setAttributes({ animateBars: v }); },
278 __nextHasNoMarginBottom: true,
279 })
280 ),
281 /* ── Layout ───────────────────────────────────────────── */
282 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: false },
283 el(RangeControl, {
284 label: __('Max width (px)', 'blockenberg'),
285 value: attributes.maxWidth,
286 min: 240, max: 900,
287 onChange: function (v) { setAttributes({ maxWidth: v }); },
288 }),
289 el(RangeControl, {
290 label: __('Padding (px)', 'blockenberg'),
291 value: attributes.wrapPadding,
292 min: 0, max: 80,
293 onChange: function (v) { setAttributes({ wrapPadding: v }); },
294 }),
295 el(RangeControl, {
296 label: __('Border radius (px)', 'blockenberg'),
297 value: attributes.borderRadius,
298 min: 0, max: 40,
299 onChange: function (v) { setAttributes({ borderRadius: v }); },
300 })
301 ),
302 /* ── Colors ───────────────────────────────────────────── */
303
304 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
305 getTypographyControl() && el(getTypographyControl(), { label: __('Question'), value: attributes.questionTypo, onChange: function (v) { setAttributes({ questionTypo: v }); } })
306 ),
307 el(PanelColorSettings, {
308 title: __('Colors', 'blockenberg'),
309 initialOpen: false,
310 colorSettings: [
311 {
312 value: attributes.barColor,
313 onChange: function (v) { setAttributes({ barColor: v || '#6c3fb5' }); },
314 label: __('Bar fill color', 'blockenberg'),
315 },
316 {
317 value: attributes.barBg,
318 onChange: function (v) { setAttributes({ barBg: v || '#ede9fe' }); },
319 label: __('Bar track color', 'blockenberg'),
320 },
321 {
322 value: attributes.buttonBg,
323 onChange: function (v) { setAttributes({ buttonBg: v || '#6c3fb5' }); },
324 label: __('Button background', 'blockenberg'),
325 },
326 {
327 value: attributes.buttonColor,
328 onChange: function (v) { setAttributes({ buttonColor: v || '#ffffff' }); },
329 label: __('Button text color', 'blockenberg'),
330 },
331 {
332 value: attributes.questionColor,
333 onChange: function (v) { setAttributes({ questionColor: v || '' }); },
334 label: __('Question color', 'blockenberg'),
335 },
336 {
337 value: attributes.resultTextColor,
338 onChange: function (v) { setAttributes({ resultTextColor: v || '' }); },
339 label: __('Result text color', 'blockenberg'),
340 },
341 {
342 value: attributes.wrapBg,
343 onChange: function (v) { setAttributes({ wrapBg: v || '' }); },
344 label: __('Card background', 'blockenberg'),
345 },
346 {
347 value: attributes.wrapBorder,
348 onChange: function (v) { setAttributes({ wrapBorder: v || '' }); },
349 label: __('Card border color', 'blockenberg'),
350 },
351 ],
352 })
353 ),
354 el('div', blockProps,
355 el(PollPreview, { attributes: attributes })
356 )
357 );
358 },
359
360 save: function (props) {
361 var a = props.attributes;
362 var options = a.options || [];
363 var optionsJson = JSON.stringify(options);
364 var _tv = getTypoCssVars();
365
366 var wrapStyle = Object.assign({
367 '--bkip-bar': a.barColor,
368 '--bkip-bar-bg': a.barBg,
369 '--bkip-btn-bg': a.buttonBg,
370 '--bkip-btn-color': a.buttonColor,
371 '--bkip-radius': a.borderRadius + 'px',
372 '--bkip-q-size': a.questionSize + 'px',
373 background: a.wrapBg || undefined,
374 border: a.wrapBorder ? '1px solid ' + a.wrapBorder : undefined,
375 borderRadius: a.borderRadius + 'px',
376 padding: a.wrapPadding + 'px',
377 maxWidth: a.maxWidth + 'px',
378 color: a.questionColor || undefined,
379 }, _tv(a.questionTypo, '--bkip-q-'));
380
381 return el('div', {
382 className: 'bkip-wrap',
383 'data-instance': a.instanceId,
384 'data-allow-revote': a.allowRevote ? 'true' : 'false',
385 'data-show-before': a.showResultsBefore ? 'true' : 'false',
386 'data-animate': a.animateBars ? 'true' : 'false',
387 style: wrapStyle,
388 },
389 el('p', { className: 'bkip-question' }, a.question),
390 el('div', { className: 'bkip-body' },
391 /* Vote area (visible before vote) */
392 el('div', { className: 'bkip-votes', 'aria-hidden': a.showResultsBefore ? 'false' : 'false' },
393 options.map(function (opt, i) {
394 return el('button', {
395 key: i,
396 className: 'bkip-option-btn',
397 'data-index': i,
398 type: 'button',
399 }, opt.text);
400 })
401 ),
402 /* Results area (hidden until vote) */
403 el('div', { className: 'bkip-results', 'aria-hidden': 'true' },
404 options.map(function (opt, i) {
405 return el('div', { key: i, className: 'bkip-result' },
406 el('div', { className: 'bkip-result-label' },
407 el('span', { className: 'bkip-result-text' }, opt.text),
408 el('span', { className: 'bkip-result-pct' }, '0%')
409 ),
410 el('div', { className: 'bkip-bar' },
411 el('div', { className: 'bkip-bar-fill', 'data-index': i })
412 )
413 );
414 }),
415 a.totalVotesLabel && el('p', { className: 'bkip-total' },
416 el('span', { className: 'bkip-total-num' }, '0'),
417 ' votes'
418 ),
419 a.allowRevote && el('button', { className: 'bkip-revote', type: 'button' },
420 'Change vote'
421 )
422 ),
423 /* JSON data for frontend */
424 el('script', {
425 type: 'application/json',
426 className: 'bkip-data',
427 dangerouslySetInnerHTML: { __html: optionsJson }
428 })
429 )
430 );
431 },
432 });
433 }() );
434