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

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

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