PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.6
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.6
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 / business-hours / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.6, at blocks/business-hours/index.js

456 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 wp.domReady(function () {
2 var el = wp.element.createElement;
3 var Fragment = wp.element.Fragment;
4 var __ = wp.i18n.__;
5 var useState = wp.element.useState;
6 var useRef = wp.element.useRef;
7 var useEffect = wp.element.useEffect;
8 var registerBlockType = wp.blocks.registerBlockType;
9 var InspectorControls = wp.blockEditor.InspectorControls;
10 var useBlockProps = wp.blockEditor.useBlockProps;
11 var PanelBody = wp.components.PanelBody;
12 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
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
18 // Convert 24h to 12h format
19 function to12h(time) {
20 if (!time) return '';
21 var parts = time.split(':');
22 var h = parseInt(parts[0], 10);
23 var m = parts[1];
24 var ampm = h >= 12 ? 'PM' : 'AM';
25 h = h % 12 || 12;
26 return h + ':' + m + ' ' + ampm;
27 }
28
29 // Get current day index (0 = Monday)
30 function getTodayIndex() {
31 var jsDay = new Date().getDay();
32 return jsDay === 0 ? 6 : jsDay - 1;
33 }
34
35 registerBlockType('blockenberg/business-hours', {
36 title: __('Business Hours', 'blockenberg'),
37 icon: 'calendar-alt',
38 category: 'blockenberg',
39 description: __('Display business opening hours with current status indicator.', 'blockenberg'),
40
41 edit: function (props) {
42 var attributes = props.attributes;
43 var setAttributes = props.setAttributes;
44 var a = attributes;
45
46 // Track which field is being edited: null, 'title', 'day-0', 'time-0', etc.
47 var editingState = useState(null);
48 var editing = editingState[0];
49 var setEditing = editingState[1];
50
51 var todayIndex = getTodayIndex();
52
53 // Update day field
54 function updateDay(index, field, value) {
55 var newDays = a.days.map(function (d, i) {
56 if (i === index) {
57 var updated = Object.assign({}, d);
58 updated[field] = value;
59 return updated;
60 }
61 return d;
62 });
63 setAttributes({ days: newDays });
64 }
65
66 // Format time for display
67 function formatTime(time) {
68 if (!time) return '--:--';
69 return a.timeFormat === '12h' ? to12h(time) : time;
70 }
71
72 var layoutOptions = [
73 { label: __('List', 'blockenberg'), value: 'list' },
74 { label: __('Cards', 'blockenberg'), value: 'cards' }
75 ];
76
77 var timeFormatOptions = [
78 { label: __('12 Hour (9:00 AM)', 'blockenberg'), value: '12h' },
79 { label: __('24 Hour (09:00)', 'blockenberg'), value: '24h' }
80 ];
81
82 var fontWeightOptions = [
83 { label: '400', value: 400 },
84 { label: '500', value: 500 },
85 { label: '600', value: 600 },
86 { label: '700', value: 700 }
87 ];
88
89 // Inspector
90 var inspector = el(InspectorControls, {},
91 el(PanelBody, { title: __('General', 'blockenberg'), initialOpen: true },
92 el(ToggleControl, {
93 label: __('Show Title', 'blockenberg'),
94 checked: a.showTitle,
95 __nextHasNoMarginBottom: true,
96 onChange: function (v) { setAttributes({ showTitle: v }); }
97 }),
98 el(ToggleControl, {
99 label: __('Show Current Status', 'blockenberg'),
100 checked: a.showCurrentStatus,
101 __nextHasNoMarginBottom: true,
102 onChange: function (v) { setAttributes({ showCurrentStatus: v }); }
103 }),
104 el(ToggleControl, {
105 label: __('Highlight Today', 'blockenberg'),
106 checked: a.highlightToday,
107 __nextHasNoMarginBottom: true,
108 onChange: function (v) { setAttributes({ highlightToday: v }); }
109 }),
110 el(ToggleControl, {
111 label: __('Show Dividers', 'blockenberg'),
112 checked: a.showDividers,
113 __nextHasNoMarginBottom: true,
114 onChange: function (v) { setAttributes({ showDividers: v }); }
115 })
116 ),
117
118 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: false },
119 el(SelectControl, {
120 label: __('Style', 'blockenberg'),
121 value: a.layoutStyle,
122 options: layoutOptions,
123 onChange: function (v) { setAttributes({ layoutStyle: v }); }
124 }),
125 el(SelectControl, {
126 label: __('Time Format', 'blockenberg'),
127 value: a.timeFormat,
128 options: timeFormatOptions,
129 onChange: function (v) { setAttributes({ timeFormat: v }); }
130 }),
131 el(RangeControl, {
132 label: __('Padding', 'blockenberg'),
133 value: a.padding,
134 min: 8,
135 max: 32,
136 onChange: function (v) { setAttributes({ padding: v }); }
137 }),
138 el(RangeControl, {
139 label: __('Gap', 'blockenberg'),
140 value: a.gap,
141 min: 4,
142 max: 24,
143 onChange: function (v) { setAttributes({ gap: v }); }
144 }),
145 el(RangeControl, {
146 label: __('Border Radius', 'blockenberg'),
147 value: a.borderRadius,
148 min: 0,
149 max: 20,
150 onChange: function (v) { setAttributes({ borderRadius: v }); }
151 })
152 ),
153
154 el(PanelBody, { title: __('Labels', 'blockenberg'), initialOpen: false },
155 el(TextControl, {
156 label: __('Closed Text', 'blockenberg'),
157 value: a.closedText,
158 onChange: function (v) { setAttributes({ closedText: v }); }
159 }),
160 el(TextControl, {
161 label: __('Open Now Text', 'blockenberg'),
162 value: a.openNowText,
163 onChange: function (v) { setAttributes({ openNowText: v }); }
164 }),
165 el(TextControl, {
166 label: __('Closed Now Text', 'blockenberg'),
167 value: a.closedNowText,
168 onChange: function (v) { setAttributes({ closedNowText: v }); }
169 })
170 ),
171
172 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
173 el(RangeControl, {
174 label: __('Title Font Size', 'blockenberg'),
175 value: a.titleFontSize,
176 min: 14,
177 max: 32,
178 onChange: function (v) { setAttributes({ titleFontSize: v }); }
179 }),
180 el(SelectControl, {
181 label: __('Title Font Weight', 'blockenberg'),
182 value: a.titleFontWeight,
183 options: fontWeightOptions,
184 onChange: function (v) { setAttributes({ titleFontWeight: parseInt(v, 10) }); }
185 }),
186 el(RangeControl, {
187 label: __('Day Font Size', 'blockenberg'),
188 value: a.dayFontSize,
189 min: 12,
190 max: 20,
191 onChange: function (v) { setAttributes({ dayFontSize: v }); }
192 }),
193 el(SelectControl, {
194 label: __('Day Font Weight', 'blockenberg'),
195 value: a.dayFontWeight,
196 options: fontWeightOptions,
197 onChange: function (v) { setAttributes({ dayFontWeight: parseInt(v, 10) }); }
198 }),
199 el(RangeControl, {
200 label: __('Time Font Size', 'blockenberg'),
201 value: a.timeFontSize,
202 min: 12,
203 max: 20,
204 onChange: function (v) { setAttributes({ timeFontSize: v }); }
205 }),
206 el(SelectControl, {
207 label: __('Time Font Weight', 'blockenberg'),
208 value: a.timeFontWeight,
209 options: fontWeightOptions,
210 onChange: function (v) { setAttributes({ timeFontWeight: parseInt(v, 10) }); }
211 })
212 ),
213
214 el(PanelColorSettings, {
215 title: __('Colors', 'blockenberg'),
216 initialOpen: false,
217 colorSettings: [
218 { value: a.dayBg, onChange: function (c) { setAttributes({ dayBg: c }); }, label: __('Background', 'blockenberg') },
219 { value: a.dayColor, onChange: function (c) { setAttributes({ dayColor: c }); }, label: __('Day Text', 'blockenberg') },
220 { value: a.timeColor, onChange: function (c) { setAttributes({ timeColor: c }); }, label: __('Time Text', 'blockenberg') },
221 { value: a.dividerColor, onChange: function (c) { setAttributes({ dividerColor: c }); }, label: __('Divider', 'blockenberg') }
222 ]
223 }),
224
225 el(PanelColorSettings, {
226 title: __('Status Colors', 'blockenberg'),
227 initialOpen: false,
228 colorSettings: [
229 { value: a.todayBg, onChange: function (c) { setAttributes({ todayBg: c }); }, label: __('Today Background', 'blockenberg') },
230 { value: a.todayColor, onChange: function (c) { setAttributes({ todayColor: c }); }, label: __('Today Text', 'blockenberg') },
231 { value: a.openColor, onChange: function (c) { setAttributes({ openColor: c }); }, label: __('Open Status', 'blockenberg') },
232 { value: a.closedColor, onChange: function (c) { setAttributes({ closedColor: c }); }, label: __('Closed Status', 'blockenberg') }
233 ]
234 })
235 );
236
237 // CSS variables
238 var wrapStyle = {
239 '--bkbg-bh-day-bg': a.dayBg,
240 '--bkbg-bh-day-color': a.dayColor,
241 '--bkbg-bh-time-color': a.timeColor,
242 '--bkbg-bh-today-bg': a.todayBg,
243 '--bkbg-bh-today-color': a.todayColor,
244 '--bkbg-bh-open-color': a.openColor,
245 '--bkbg-bh-closed-color': a.closedColor,
246 '--bkbg-bh-divider-color': a.dividerColor,
247 '--bkbg-bh-radius': a.borderRadius + 'px',
248 '--bkbg-bh-padding': a.padding + 'px',
249 '--bkbg-bh-gap': a.gap + 'px',
250 '--bkbg-bh-day-font-size': a.dayFontSize + 'px',
251 '--bkbg-bh-day-font-weight': a.dayFontWeight,
252 '--bkbg-bh-time-font-size': a.timeFontSize + 'px',
253 '--bkbg-bh-time-font-weight': a.timeFontWeight,
254 '--bkbg-bh-title-font-size': a.titleFontSize + 'px',
255 '--bkbg-bh-title-font-weight': a.titleFontWeight
256 };
257
258 // Title - click to edit
259 var titleEl = null;
260 if (a.showTitle) {
261 if (editing === 'title') {
262 titleEl = el('input', {
263 type: 'text',
264 className: 'bkbg-bh-title bkbg-bh-input-active',
265 value: a.title,
266 autoFocus: true,
267 onChange: function (e) { setAttributes({ title: e.target.value }); },
268 onBlur: function () { setEditing(null); },
269 onKeyDown: function (e) { if (e.key === 'Enter') setEditing(null); },
270 placeholder: __('Business Hours', 'blockenberg')
271 });
272 } else {
273 titleEl = el('div', {
274 className: 'bkbg-bh-title bkbg-bh-clickable',
275 onClick: function () { setEditing('title'); }
276 }, a.title || __('Business Hours', 'blockenberg'));
277 }
278 }
279
280 // Build rows - WYSIWYG style
281 var rows = a.days.map(function (day, index) {
282 var isToday = index === todayIndex;
283 var rowClass = 'bkbg-bh-row' + (isToday && a.highlightToday ? ' is-today' : '');
284 var dayKey = 'day-' + index;
285 var timeKey = 'time-' + index;
286
287 // Day name - click to edit
288 var dayNameEl;
289 if (editing === dayKey) {
290 dayNameEl = el('input', {
291 type: 'text',
292 className: 'bkbg-bh-day-name bkbg-bh-input-active',
293 value: day.day,
294 autoFocus: true,
295 onChange: function (e) { updateDay(index, 'day', e.target.value); },
296 onBlur: function () { setEditing(null); },
297 onKeyDown: function (e) { if (e.key === 'Enter') setEditing(null); }
298 });
299 } else {
300 dayNameEl = el('span', {
301 className: 'bkbg-bh-day-name bkbg-bh-clickable',
302 onClick: function () { setEditing(dayKey); }
303 }, day.day);
304 }
305
306 // Time - click to edit
307 var timeEl;
308 if (editing === timeKey) {
309 // Show time inputs when editing
310 timeEl = el('div', { className: 'bkbg-bh-time-editing' },
311 el('label', { className: 'bkbg-bh-closed-check' },
312 el('input', {
313 type: 'checkbox',
314 checked: day.closed,
315 onChange: function (e) { updateDay(index, 'closed', e.target.checked); }
316 }),
317 el('span', {}, a.closedText)
318 ),
319 !day.closed && el('div', { className: 'bkbg-bh-time-inputs' },
320 el('input', {
321 type: 'time',
322 value: day.open,
323 onChange: function (e) { updateDay(index, 'open', e.target.value); }
324 }),
325 el('span', {}, ''),
326 el('input', {
327 type: 'time',
328 value: day.close,
329 onChange: function (e) { updateDay(index, 'close', e.target.value); }
330 })
331 ),
332 el('button', {
333 className: 'bkbg-bh-done-btn',
334 onClick: function () { setEditing(null); }
335 }, '')
336 );
337 } else {
338 // Show formatted time as text
339 var timeText = day.closed ? a.closedText : formatTime(day.open) + '' + formatTime(day.close);
340 timeEl = el('div', {
341 className: 'bkbg-bh-time bkbg-bh-clickable' + (day.closed ? ' is-closed' : ''),
342 onClick: function () { setEditing(timeKey); }
343 }, timeText);
344 }
345
346 return el('div', { className: rowClass, key: index },
347 el('div', { className: 'bkbg-bh-day' },
348 dayNameEl,
349 isToday && a.highlightToday && el('span', { className: 'bkbg-bh-today-badge' }, __('Today', 'blockenberg'))
350 ),
351 timeEl
352 );
353 });
354
355 var blockProps = useBlockProps({
356 className: 'bkbg-editor-wrap',
357 'data-block-label': 'Business Hours'
358 });
359
360 return el('div', blockProps,
361 inspector,
362 el('div', {
363 className: 'bkbg-bh-wrap',
364 style: wrapStyle,
365 'data-layout': a.layoutStyle,
366 'data-dividers': a.showDividers ? '1' : '0'
367 },
368 titleEl,
369 a.showCurrentStatus && el('div', { className: 'bkbg-bh-status is-open' },
370 el('span', { className: 'bkbg-bh-status-dot' }),
371 el('span', { className: 'bkbg-bh-status-text' }, a.openNowText)
372 ),
373 el('div', { className: 'bkbg-bh-list' }, rows)
374 )
375 );
376 },
377
378 save: function (props) {
379 var a = props.attributes;
380
381 function to12h(time) {
382 if (!time) return '';
383 var parts = time.split(':');
384 var h = parseInt(parts[0], 10);
385 var m = parts[1];
386 var ampm = h >= 12 ? 'PM' : 'AM';
387 h = h % 12 || 12;
388 return h + ':' + m + ' ' + ampm;
389 }
390
391 function formatTime(time) {
392 if (!time) return '';
393 return a.timeFormat === '12h' ? to12h(time) : time;
394 }
395
396 var wrapStyle = {
397 '--bkbg-bh-day-bg': a.dayBg,
398 '--bkbg-bh-day-color': a.dayColor,
399 '--bkbg-bh-time-color': a.timeColor,
400 '--bkbg-bh-today-bg': a.todayBg,
401 '--bkbg-bh-today-color': a.todayColor,
402 '--bkbg-bh-open-color': a.openColor,
403 '--bkbg-bh-closed-color': a.closedColor,
404 '--bkbg-bh-divider-color': a.dividerColor,
405 '--bkbg-bh-radius': a.borderRadius + 'px',
406 '--bkbg-bh-padding': a.padding + 'px',
407 '--bkbg-bh-gap': a.gap + 'px',
408 '--bkbg-bh-day-font-size': a.dayFontSize + 'px',
409 '--bkbg-bh-day-font-weight': a.dayFontWeight,
410 '--bkbg-bh-time-font-size': a.timeFontSize + 'px',
411 '--bkbg-bh-time-font-weight': a.timeFontWeight,
412 '--bkbg-bh-title-font-size': a.titleFontSize + 'px',
413 '--bkbg-bh-title-font-weight': a.titleFontWeight
414 };
415
416 var rows = a.days.map(function (day, index) {
417 return el('div', {
418 className: 'bkbg-bh-row',
419 key: index,
420 'data-day': index,
421 'data-open': day.open,
422 'data-close': day.close,
423 'data-closed': day.closed ? '1' : '0'
424 },
425 el('div', { className: 'bkbg-bh-day' },
426 el('span', { className: 'bkbg-bh-day-name' }, day.day)
427 ),
428 el('div', { className: 'bkbg-bh-time' },
429 day.closed
430 ? el('span', { className: 'bkbg-bh-closed-text' }, a.closedText)
431 : el('span', {}, formatTime(day.open) + '' + formatTime(day.close))
432 )
433 );
434 });
435
436 return el('div', {
437 className: 'bkbg-bh-wrap',
438 style: wrapStyle,
439 'data-layout': a.layoutStyle,
440 'data-dividers': a.showDividers ? '1' : '0',
441 'data-highlight-today': a.highlightToday ? '1' : '0',
442 'data-show-status': a.showCurrentStatus ? '1' : '0',
443 'data-open-text': a.openNowText,
444 'data-closed-text': a.closedNowText
445 },
446 a.showTitle && el('div', { className: 'bkbg-bh-title' }, a.title),
447 a.showCurrentStatus && el('div', { className: 'bkbg-bh-status', 'data-status': '1' },
448 el('span', { className: 'bkbg-bh-status-dot' }),
449 el('span', { className: 'bkbg-bh-status-text' })
450 ),
451 el('div', { className: 'bkbg-bh-list' }, rows)
452 );
453 }
454 });
455 });
456