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

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

271 lines 19.7 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 __ = wp.i18n.__;
4 var registerBlockType = wp.blocks.registerBlockType;
5 var useBlockProps = wp.blockEditor.useBlockProps;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
8 var PanelBody = wp.components.PanelBody;
9 var TextControl = wp.components.TextControl;
10 var TextareaControl = wp.components.TextareaControl;
11 var ToggleControl = wp.components.ToggleControl;
12 var RangeControl = wp.components.RangeControl;
13 var Button = wp.components.Button;
14 var useState = wp.element.useState;
15
16 function getTypographyControl() {
17 return (window.bkbgTypographyControl || function () { return null; });
18 }
19
20 function _tv(typo, prefix) {
21 if (!typo) return {};
22 var s = {};
23 if (typo.family) s[prefix + 'font-family'] = "'" + typo.family + "', sans-serif";
24 if (typo.weight) s[prefix + 'font-weight'] = typo.weight;
25 if (typo.transform) s[prefix + 'text-transform'] = typo.transform;
26 if (typo.style) s[prefix + 'font-style'] = typo.style;
27 if (typo.decoration) s[prefix + 'text-decoration'] = typo.decoration;
28 var su = typo.sizeUnit || 'px';
29 if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) s[prefix + 'font-size-d'] = typo.sizeDesktop + su;
30 if (typo.sizeTablet !== '' && typo.sizeTablet != null) s[prefix + 'font-size-t'] = typo.sizeTablet + su;
31 if (typo.sizeMobile !== '' && typo.sizeMobile != null) s[prefix + 'font-size-m'] = typo.sizeMobile + su;
32 var lhu = typo.lineHeightUnit || '';
33 if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) s[prefix + 'line-height-d'] = typo.lineHeightDesktop + lhu;
34 if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) s[prefix + 'line-height-t'] = typo.lineHeightTablet + lhu;
35 if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) s[prefix + 'line-height-m'] = typo.lineHeightMobile + lhu;
36 var lsu = typo.letterSpacingUnit || 'px';
37 if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) s[prefix + 'letter-spacing-d'] = typo.letterSpacingDesktop + lsu;
38 if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) s[prefix + 'letter-spacing-t'] = typo.letterSpacingTablet + lsu;
39 if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) s[prefix + 'letter-spacing-m'] = typo.letterSpacingMobile + lsu;
40 var wsu = typo.wordSpacingUnit || 'px';
41 if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) s[prefix + 'word-spacing-d'] = typo.wordSpacingDesktop + wsu;
42 if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) s[prefix + 'word-spacing-t'] = typo.wordSpacingTablet + wsu;
43 if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) s[prefix + 'word-spacing-m'] = typo.wordSpacingMobile + wsu;
44 return s;
45 }
46
47 var DAYS_SHORT = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
48 var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
49
50 // ── Calendar preview ──
51 function CalendarPreview(props) {
52 var attr = props.attr;
53 var today = new Date(2026, 1, 23); // Fixed demo date
54 var year = today.getFullYear();
55 var month = today.getMonth();
56 var firstDay = new Date(year, month, 1).getDay();
57 var daysInMonth = new Date(year, month + 1, 0).getDate();
58 var blocked = attr.blockedDays || [0, 6];
59
60 var cells = [];
61 for (var pad = 0; pad < firstDay; pad++) {
62 cells.push(el('div', { key: 'p' + pad, style: { padding: '8px 4px', fontSize: 13, color: 'transparent' } }, '·'));
63 }
64 for (var d = 1; d <= daysInMonth; d++) {
65 var dow = (firstDay + d - 1) % 7;
66 var isBlocked = blocked.indexOf(dow) !== -1;
67 var isSel = d === today.getDate();
68 cells.push(el('div', {
69 key: d,
70 style: {
71 padding: '8px 4px', textAlign: 'center', borderRadius: 8, fontSize: 13, fontWeight: 500,
72 background: isSel ? attr.selectedDayBg : 'transparent',
73 color: isSel ? attr.selectedDayColor : isBlocked ? '#cbd5e1' : attr.textColor,
74 cursor: isBlocked ? 'default' : 'pointer',
75 opacity: isBlocked ? 0.4 : 1
76 }
77 }, d));
78 }
79
80 return el('div', { style: { background: attr.calendarBg, borderRadius: attr.calendarRadius + 'px', overflow: 'hidden', marginBottom: 20 } },
81 // Header
82 el('div', { style: { background: attr.calendarHeaderBg, color: attr.calendarHeaderColor, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 20px' } },
83 el('button', { style: { background: 'none', border: 'none', color: 'inherit', fontSize: 18, cursor: 'pointer', padding: '0 4px' } }, ''),
84 el('span', { style: { fontWeight: 700, fontSize: 15 } }, MONTHS[month] + ' ' + year),
85 el('button', { style: { background: 'none', border: 'none', color: 'inherit', fontSize: 18, cursor: 'pointer', padding: '0 4px' } }, '')
86 ),
87 // Day names
88 el('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', padding: '8px 12px 0', gap: 2 } },
89 DAYS_SHORT.map(function(d) { return el('div', { key: d, style: { textAlign: 'center', fontSize: 11, fontWeight: 700, color: attr.mutedColor, padding: '4px 0' } }, d); })
90 ),
91 // Cells
92 el('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', padding: '4px 12px 12px', gap: 2 } }, ...cells)
93 );
94 }
95
96 registerBlockType('blockenberg/booking-form', {
97 edit: function (props) {
98 var attr = props.attributes;
99 var setAttr = props.setAttributes;
100 var blockProps = useBlockProps({ style: Object.assign({ background: attr.sectionBg || undefined }, _tv(attr.typoTitle, '--bkbg-bfm-title-'), _tv(attr.typoSubtitle, '--bkbg-bfm-sub-')) });
101
102 // Service list editor
103 function ServiceRow(srProps) {
104 var s = srProps.s;
105 var idx = srProps.idx;
106 function update(patch) {
107 var svc = JSON.parse(JSON.stringify(attr.services));
108 Object.assign(svc[idx], patch);
109 setAttr({ services: svc });
110 }
111 return el('div', { style: { border: '1px solid #e2e8f0', borderRadius: 8, padding: '8px 10px', marginBottom: 8 } },
112 el(TextControl, { label: 'Service name', value: s.label, onChange: function(v){ update({ label: v }); }, __nextHasNoMarginBottom: true }),
113 el('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 6 } },
114 el(TextControl, { label: 'Duration (min)', value: String(s.duration), onChange: function(v){ update({ duration: parseInt(v) || 0 }); }, __nextHasNoMarginBottom: true }),
115 el(TextControl, { label: 'Price', value: s.price, onChange: function(v){ update({ price: v }); }, __nextHasNoMarginBottom: true })
116 ),
117 el(Button, {
118 variant: 'link', isDestructive: true, size: 'small', style: { marginTop: 6 },
119 onClick: function() { var svc = JSON.parse(JSON.stringify(attr.services)); svc.splice(idx, 1); setAttr({ services: svc }); }
120 }, '✕ Remove service')
121 );
122 }
123
124 // Time slots editor
125 function updateSlots(rawText) {
126 var slots = rawText.split('\n').map(function(s){ return s.trim(); }).filter(Boolean);
127 setAttr({ timeSlots: slots });
128 }
129
130 return el('div', blockProps,
131 el(InspectorControls, {},
132
133 // Content
134 el(PanelBody, { title: 'Form Content', initialOpen: true },
135 el(TextControl, { label: 'Form Title', value: attr.formTitle, onChange: function(v){ setAttr({ formTitle: v }); }, __nextHasNoMarginBottom: true }),
136 el(TextControl, { label: 'Form Subtitle', value: attr.formSubtitle, onChange: function(v){ setAttr({ formSubtitle: v }); }, __nextHasNoMarginBottom: true }),
137 el(TextControl, { label: 'Submit Button Label', value: attr.submitLabel, onChange: function(v){ setAttr({ submitLabel: v }); }, __nextHasNoMarginBottom: true }),
138 el(TextControl, { label: 'Confirmation Title', value: attr.confirmationTitle, onChange: function(v){ setAttr({ confirmationTitle: v }); }, __nextHasNoMarginBottom: true }),
139 el(TextareaControl, { label: 'Confirmation Message', value: attr.confirmationMsg, onChange: function(v){ setAttr({ confirmationMsg: v }); }, __nextHasNoMarginBottom: true })
140 ),
141
142 // Services
143 el(PanelBody, { title: 'Services', initialOpen: false },
144 el(ToggleControl, { label: 'Show service selector', checked: attr.showServiceSelect, onChange: function(v){ setAttr({ showServiceSelect: v }); }, __nextHasNoMarginBottom: true }),
145 el(ToggleControl, { label: 'Show price', checked: attr.showPrice, onChange: function(v){ setAttr({ showPrice: v }); }, __nextHasNoMarginBottom: true }),
146 el('hr', { style: { borderColor: '#e2e8f0', margin: '8px 0' } }),
147 attr.services.map(function(s, i) { return el(ServiceRow, { key: i, s: s, idx: i }); }),
148 el(Button, {
149 variant: 'secondary', size: 'small',
150 onClick: function() { var svc = JSON.parse(JSON.stringify(attr.services)); svc.push({ label: 'New Service', duration: 60, price: '' }); setAttr({ services: svc }); }
151 }, '+ Add Service')
152 ),
153
154 // Time slots
155 el(PanelBody, { title: 'Time Slots', initialOpen: false },
156 el(TextareaControl, {
157 label: 'Time slots (one per line, HH:MM)',
158 value: (attr.timeSlots || []).join('\n'),
159 onChange: updateSlots,
160 rows: 8,
161 __nextHasNoMarginBottom: true
162 }),
163 el('p', { style: { fontSize: 11, color: '#64748b', margin: '4px 0 0' } }, 'Blocked days (0=Sun, 6=Sat): ' + (attr.blockedDays || []).join(', '))
164 ),
165
166 // Fields
167 el(PanelBody, { title: 'Form Fields', initialOpen: false },
168 el(ToggleControl, { label: 'Name field', checked: attr.showNameField, onChange: function(v){ setAttr({ showNameField: v }); }, __nextHasNoMarginBottom: true }),
169 el(ToggleControl, { label: 'Email field', checked: attr.showEmailField, onChange: function(v){ setAttr({ showEmailField: v }); }, __nextHasNoMarginBottom: true }),
170 el(ToggleControl, { label: 'Phone field', checked: attr.showPhoneField, onChange: function(v){ setAttr({ showPhoneField: v }); }, __nextHasNoMarginBottom: true }),
171 el(ToggleControl, { label: 'Notes / message field', checked: attr.showNotes, onChange: function(v){ setAttr({ showNotes: v }); }, __nextHasNoMarginBottom: true })
172 ),
173
174 // Layout
175 el(PanelBody, { title: 'Layout', initialOpen: false },
176 el(RangeControl, { label: 'Form Width (px)', value: attr.formWidth, min: 360, max: 900, step: 10, onChange: function(v){ setAttr({ formWidth: v }); }, __nextHasNoMarginBottom: true }),
177 el(RangeControl, { label: 'Form Radius (px)', value: attr.formRadius, min: 0, max: 40, onChange: function(v){ setAttr({ formRadius: v }); }, __nextHasNoMarginBottom: true }),
178 el(RangeControl, { label: 'Input Radius (px)', value: attr.inputRadius, min: 0, max: 24, onChange: function(v){ setAttr({ inputRadius: v }); }, __nextHasNoMarginBottom: true }),
179 el(RangeControl, { label: 'Calendar Radius (px)', value: attr.calendarRadius, min: 0, max: 24, onChange: function(v){ setAttr({ calendarRadius: v }); }, __nextHasNoMarginBottom: true })
180 ),
181
182 // Typography
183 el(PanelBody, { title: 'Typography', initialOpen: false },
184 el(getTypographyControl(), { label: __('Title', 'blockenberg'), value: attr.typoTitle, onChange: function (v) { setAttr({ typoTitle: v }); } }),
185 el(getTypographyControl(), { label: __('Subtitle', 'blockenberg'), value: attr.typoSubtitle, onChange: function (v) { setAttr({ typoSubtitle: v }); } })
186 ),
187
188 // Colors
189 el(PanelColorSettings, {
190 title: 'Colors',
191 initialOpen: false,
192 colorSettings: [
193 { label: 'Form Background', value: attr.formBg, onChange: function(v){ setAttr({ formBg: v || '#ffffff' }); } },
194 { label: 'Form Border', value: attr.formBorder, onChange: function(v){ setAttr({ formBorder: v || '#e2e8f0' }); } },
195 { label: 'Calendar Background', value: attr.calendarBg, onChange: function(v){ setAttr({ calendarBg: v || '#f8fafc' }); } },
196 { label: 'Calendar Header BG', value: attr.calendarHeaderBg, onChange: function(v){ setAttr({ calendarHeaderBg: v || '#6366f1' }); } },
197 { label: 'Calendar Header Text', value: attr.calendarHeaderColor,onChange: function(v){ setAttr({ calendarHeaderColor:v || '#ffffff' }); } },
198 { label: 'Selected Day BG', value: attr.selectedDayBg, onChange: function(v){ setAttr({ selectedDayBg: v || '#6366f1' }); } },
199 { label: 'Selected Day Text', value: attr.selectedDayColor, onChange: function(v){ setAttr({ selectedDayColor: v || '#ffffff' }); } },
200 { label: 'Time Slot BG', value: attr.slotBg, onChange: function(v){ setAttr({ slotBg: v || '#f1f5f9' }); } },
201 { label: 'Selected Slot BG', value: attr.slotSelectedBg, onChange: function(v){ setAttr({ slotSelectedBg: v || '#6366f1' }); } },
202 { label: 'Selected Slot Text', value: attr.slotSelectedColor, onChange: function(v){ setAttr({ slotSelectedColor: v || '#ffffff' }); } },
203 { label: 'Input Background', value: attr.inputBg, onChange: function(v){ setAttr({ inputBg: v || '#f8fafc' }); } },
204 { label: 'Input Border', value: attr.inputBorder, onChange: function(v){ setAttr({ inputBorder: v || '#e2e8f0' }); } },
205 { label: 'Submit Button BG', value: attr.btnBg, onChange: function(v){ setAttr({ btnBg: v || '#6366f1' }); } },
206 { label: 'Submit Button Text', value: attr.btnColor, onChange: function(v){ setAttr({ btnColor: v || '#ffffff' }); } },
207 { label: 'Text Color', value: attr.textColor, onChange: function(v){ setAttr({ textColor: v || '#0f172a' }); } },
208 { label: 'Muted Color', value: attr.mutedColor, onChange: function(v){ setAttr({ mutedColor: v || '#64748b' }); } },
209 { label: 'Section Background', value: attr.sectionBg, onChange: function(v){ setAttr({ sectionBg: v || '' }); } }
210 ]
211 })
212 ),
213
214 // ── Editor preview ──
215 el('div', { style: { maxWidth: attr.formWidth + 'px', margin: '0 auto', fontFamily: 'system-ui,sans-serif' } },
216 el('div', { style: { background: attr.formBg, border: '1px solid ' + attr.formBorder, borderRadius: attr.formRadius + 'px', padding: '28px 28px 24px', boxShadow: '0 4px 24px rgba(0,0,0,0.06)' } },
217 // Header
218 el('h2', { className: 'bkbg-bfm-title', style: { color: attr.textColor, margin: '0 0 6px' } }, attr.formTitle),
219 el('p', { className: 'bkbg-bfm-subtitle', style: { color: attr.mutedColor, margin: '0 0 24px' } }, attr.formSubtitle),
220
221 // Service select preview
222 attr.showServiceSelect && el('div', { style: { marginBottom: 18 } },
223 el('label', { style: { display: 'block', fontSize: 13, fontWeight: 600, color: attr.textColor, marginBottom: 6 } }, 'Select Service'),
224 el('select', { style: { width: '100%', padding: '11px 13px', borderRadius: attr.inputRadius + 'px', border: '1px solid ' + attr.inputBorder, background: attr.inputBg, color: attr.textColor, fontSize: 14 } },
225 attr.services.map(function(s, i) {
226 return el('option', { key: i }, s.label + (attr.showPrice && s.price ? '' + s.price : ''));
227 })
228 )
229 ),
230
231 // Calendar preview
232 el(CalendarPreview, { attr: attr }),
233
234 // Time slots
235 el('div', { style: { marginBottom: 18 } },
236 el('p', { style: { fontSize: 13, fontWeight: 600, color: attr.textColor, margin: '0 0 10px' } }, 'Available Times'),
237 el('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
238 (attr.timeSlots || []).slice(0, 8).map(function(t, i) {
239 return el('div', {
240 key: i,
241 style: {
242 padding: '7px 14px', borderRadius: attr.inputRadius + 'px', fontSize: 13, fontWeight: 500, cursor: 'pointer',
243 background: i === 0 ? attr.slotSelectedBg : attr.slotBg,
244 color: i === 0 ? attr.slotSelectedColor : attr.textColor
245 }
246 }, t);
247 }),
248 (attr.timeSlots || []).length > 8 && el('div', { style: { padding: '7px 14px', fontSize: 12, color: attr.mutedColor } }, '+' + ((attr.timeSlots.length - 8)) + ' more')
249 )
250 ),
251
252 // Submit
253 el('button', { style: { width: '100%', padding: '13px', background: attr.btnBg, color: attr.btnColor, border: 'none', borderRadius: attr.inputRadius + 'px', fontSize: 15, fontWeight: 700, cursor: 'pointer' } }, attr.submitLabel)
254 )
255 )
256 );
257 },
258
259 save: function (props) {
260 var attr = props.attributes;
261 var blockProps = useBlockProps.save();
262 return el('div', blockProps,
263 el('div', {
264 className: 'bkbg-bf-app',
265 'data-opts': JSON.stringify(attr)
266 })
267 );
268 }
269 });
270 }() );
271