00days00hours:00minutes:00seconds
00days00hours–00minutes–00seconds
block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wlc/countdown",
"title": " Countdown",
"category": "theme",
"icon": "dashboard",
"description": "Countdown",
"keywords": [ "countdown" ],
"attributes":{
"timer": {
"type": "string"
},
"timerSeparator": {
"type": "string"
},
"displayLabels": {
"type": "boolean",
"default": true
},
"displayDays": {
"type": "boolean",
"default": true
},
"align": {
"type": "string",
"default": "center"
},
"style": {
"type": "object",
"default": {
"typography": {
"fontSize": "6rem",
"fontWeight": "400"
}
}
},
"labelsStyle": {
"type": "object",
"default": {
"fontSize": "2rem",
"fontWeight": "400",
"color": ""
}
},
"separatorStyle": {
"type": "object",
"default": {
"fontSize": "6rem",
"fontWeight": "400",
"color": "",
"transform": "translateY(0)"
}
}
},
"supports": {
"anchor": true,
"align": true,
"color": true,
"spacing": {
"margin": [ "vertical" ],
"padding": true
},
"typography": {
"fontSize": true,
"lineHeight": true
},
"renaming": true
},
"version": "1.0.0",
"textdomain": "WLC",
"editorScript": "file:./index.js",
"script": "file:./view.js",
"style": "file:./style-index.css"
}edit.js
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import {
PanelBody,
DateTimePicker,
TextControl,
ToggleControl,
RangeControl,
FontSizePicker,
ColorPalette,
Dropdown,
Button,
ColorIndicator,
BaseControl
} from '@wordpress/components';
import { __, _n } from '@wordpress/i18n';
import { dateI18n } from '@wordpress/date';
import { select } from '@wordpress/data';
const edit = ( { attributes, setAttributes } ) => {
const { timer, timerSeparator, displayLabels, displayDays, labelsStyle, separatorStyle } = attributes;
const dataProps = JSON.stringify( { timer, displayLabels, displayDays } );
const blockProps = useBlockProps({ className: 'wlc-countdown', 'data-props': dataProps });
const globalSettings = select( "core/editor" ).getEditorSettings();
const labelsStyleProps = {
style: {...labelsStyle }
}
const separatorStyleProps = {
style: { ...separatorStyle }
}
const isPastDate = () => {
return Date.parse( timer ) < Date.parse( new Date() );
}
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Countdown Block Settings', 'WLC' ) }>
<BaseControl>
<BaseControl.VisualLabel>{ __( 'time and date', 'WLC' ) }</BaseControl.VisualLabel>
<p>
{ __( 'Selected date and time:', 'WLC' ) }<br/>
<strong>{ dateI18n( 'j F Y H:i', timer ) }</strong>
</p>
{ !!isPastDate() && (
<p><strong>{ __( 'WARNING! Selected Date is in past', 'WLC' ) }</strong></p>
) }
<Dropdown
popoverProps={ { placement: 'left-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
variant="primary"
onClick={ onToggle }
aria-expanded={ isOpen }
>
{ __( 'Set time and date', 'WLC' ) }
</Button>
) }
renderContent={ () =>
<DateTimePicker
currentDate={ timer }
onChange={ value => setAttributes( { timer: value } ) }
/>
}
/>
</BaseControl>
<TextControl
label={ __( 'Separator', 'WLC' ) }
value={ timerSeparator }
onChange={ value => setAttributes( { timerSeparator: value } ) }
/>
<BaseControl>
<BaseControl.VisualLabel>{ __( 'Labels', 'WLC' ) }</BaseControl.VisualLabel>
<ToggleControl
label={ __( 'Display Labels', 'WLC' ) }
checked={ displayLabels }
onChange={ value => setAttributes( { displayLabels: value } ) }
/>
</BaseControl>
<BaseControl>
<BaseControl.VisualLabel>{ __( 'Days', 'WLC' ) }</BaseControl.VisualLabel>
<ToggleControl
label={ __( 'Display Days if none left (at 0)', 'WLC' ) }
checked={ displayDays }
onChange={ value => setAttributes( { displayDays: value } ) }
/>
</BaseControl>
</PanelBody>
</InspectorControls>
<InspectorControls group="styles">
<PanelBody title={ __( 'Labels Styles', 'WLC' ) }>
<FontSizePicker
value={ labelsStyle.fontSize }
fontSizes={ globalSettings.fontSizes }
withSlider={ true }
withReset={ false }
units={ [ 'px', 'em', 'rem' ] }
onChange={ value => setAttributes( { labelsStyle: { ...labelsStyle, fontSize: value } } ) }
/>
<Dropdown
popoverProps={ { placement: 'left-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
onClick={ onToggle }
aria-expanded={ isOpen }
>
<ColorIndicator colorValue={labelsStyle.color} /> { __( 'Select color', 'WLC' ) }
</Button>
) }
renderContent={ () =>
<ColorPalette
value={labelsStyle.color}
colors= { globalSettings.colors }
onChange={ value => setAttributes( { labelsStyle: { ...labelsStyle, color: value } } ) }
enableAlpha
clearable
defaultValue="#000"
/>
}
/>
</PanelBody>
<PanelBody title={ __( 'Separator Styles', 'WLC' ) }>
<FontSizePicker
value={ separatorStyle.fontSize }
fontSizes={ globalSettings.fontSizes }
withSlider={ true }
withReset={ false }
units={ [ 'px', 'em', 'rem' ] }
onChange={ value => setAttributes( { separatorStyle: { ...separatorStyle, fontSize: value } } ) }
/>
<RangeControl
label={ __( 'Vertical position', 'WLC' ) }
value={ parseInt( separatorStyle.transform.slice(12,-1) ) * (-1) }
onChange={ value => setAttributes( { separatorStyle: { ...separatorStyle, transform: ` translateY(${value * (-1) }%)` } } ) }
min={ 0 }
max={ 100 }
marks={ [
{
value: 0,
label: '0'
},
{
value: 100,
label: '-100%'
}
] }
/>
<Dropdown
popoverProps={ { placement: 'left-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
onClick={ onToggle }
aria-expanded={ isOpen }
>
<ColorIndicator colorValue={separatorStyle.color} /> { __( 'Select color', 'WLC' ) }
</Button>
) }
renderContent={ () =>
<ColorPalette
value={separatorStyle.color}
colors={ globalSettings.colors }
onChange={ value => setAttributes( { separatorStyle: { ...separatorStyle, color: value } } ) }
enableAlpha
clearable
defaultValue="#000"
/>
}
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<div className="wlc-countdown__timer">
<span className="time days">
<span className="timer">00</span>
{ displayLabels && (
<span className="label" { ...labelsStyleProps}>{ __( 'days', 'WLC' ) }</span>
) }
</span>
<span className="time hours">
<span className="timer">00</span>
{ displayLabels && (
<span className="label" { ...labelsStyleProps}>{ __( 'hours', 'WLC' ) }</span>
) }
</span>
{ !!timerSeparator && (
<span className="separator" { ...separatorStyleProps}>
{ timerSeparator }
</span>
) }
<span className="time minutes">
<span className="timer">00</span>
{ displayLabels && (
<span className="label" { ...labelsStyleProps}>{ __( 'minutes', 'WLC' ) }</span>
) }
</span>
{ !!timerSeparator && (
<span className="separator" { ...separatorStyleProps}>
{ timerSeparator }
</span>
) }
<span className="time seconds">
<span className="timer">00</span>
{ displayLabels && (
<span className="label" { ...labelsStyleProps}>{ __( 'seconds', 'WLC' ) }</span>
) }
</span>
</div>
</div>
</>
);
}
export default edit;
index.js
import { registerBlockType } from "@wordpress/blocks";
import metadata from './block.json';
import save from "./save";
import edit from './edit';
import './style.css';
registerBlockType( metadata, {
edit: edit,
save: save,
});save.js
import { useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
const save = ( { attributes } ) => {
const { timer, timerSeparator, displayLabels, displayDays, labelsStyle, separatorStyle } = attributes;
const dataProps = JSON.stringify( { timer, displayLabels, displayDays } );
const blockProps = useBlockProps.save({ className: 'wlc-countdown', 'data-props': dataProps });
const labelsStyleProps = {
style: {...labelsStyle }
}
const separatorStyleProps = {
style: { ...separatorStyle }
}
return (
<div { ...blockProps }>
<div className="wlc-countdown__timer">
<span className="time days">
<span className="timer">00</span>
{ displayLabels && (
<span className="label" { ...labelsStyleProps}>{ __( 'days', 'WLC' ) }</span>
) }
</span>
<span className="time hours">
<span className="timer">00</span>
{ displayLabels && (
<span className="label" { ...labelsStyleProps}>{ __( 'hours', 'WLC' ) }</span>
) }
</span>
{ !!timerSeparator && (
<span className="separator" { ...separatorStyleProps}>{ timerSeparator }</span>
) }
<span className="time minutes">
<span className="timer">00</span>
{ displayLabels && (
<span className="label" { ...labelsStyleProps}>{ __( 'minutes', 'WLC' ) }</span>
) }
</span>
{ !!timerSeparator && (
<span className="separator" { ...separatorStyleProps}>{ timerSeparator }</span>
) }
<span className="time seconds">
<span className="timer">00</span>
{ displayLabels && (
<span className="label" { ...labelsStyleProps}>{ __( 'seconds', 'WLC' ) }</span>
) }
</span>
</div>
</div>
);
}
export default save;style.css
.wlc-countdown {
& .wlc-countdown__timer {
@apply flex flex-wrap gap-2 justify-center items-baseline;
& > .time {
@apply block text-center max-lg:min-w-[0.5em] lg:min-w-[1.5em];
&.days {
@apply max-lg:mr-4 lg:mr-8;
}
& > .timer {
@apply block max-lg:!text-30;
}
& > .label {
@apply block max-lg:!text-16;
}
}
& .separator {
@apply max-lg:!text-30 max-lg:!translate-y-0;
}
}
}view.js
import { _n } from '@wordpress/i18n';
const wlcCountdown = () => {
document.querySelectorAll('.wlc-countdown').forEach(countdownTimer => {
const data = countdownTimer.dataset?.props;
if ( !data ) return;
const props = JSON.parse( data );
const timer = props.timer || Date.now();
const labels = props.displayLabels;
const zeroDays = props.displayDays;
const deadline = new Date( timer );
const timers = countdownTimer.querySelector('.wlc-countdown__timer');
const daysTimer = timers.querySelector('.days');
const hoursTimer = timers.querySelector('.hours');
const minutesTimer = timers.querySelector('.minutes');
const secondsTimer = timers.querySelector('.seconds');
function count() {
const total = Date.parse( deadline ) - Date.parse( new Date() );
const days = Math.max( 0, Math.floor( total/(24*60*60*1000) ) );
const hours = Math.max( 0, Math.floor( ( total/(60*60*1000) ) % 24 ) );
const minutes = Math.max( 0, Math.floor( ( total/1000/60 ) % 60 ) ) ;
const seconds = Math.max( 0, Math.floor( ( total/1000 ) % 60 ) );
daysTimer.style.display = ( days > 0 || ( days == 0 && zeroDays ) ) ? "block" : "none";
daysTimer.querySelector('.timer').textContent = days;
hoursTimer.querySelector('.timer').textContent = hours.toString().padStart(2, '0');
minutesTimer.querySelector('.timer').textContent = minutes.toString().padStart(2, '0');
secondsTimer.querySelector('.timer').textContent = seconds.toString().padStart(2, '0');
if ( labels ) {
if (days > 0 || ( days == 0 && zeroDays ) ) {
daysTimer.querySelector('.label').textContent = _n( 'day', 'days', days, 'WLC' );
}
hoursTimer.querySelector('.label').textContent = _n( 'hour', 'hours', hours, 'WLC' );
minutesTimer.querySelector('.label').textContent = _n( 'minute', 'minutes', minutes, 'WLC' );
secondsTimer.querySelector('.label').textContent = _n( 'second', 'seconds', seconds, 'WLC' );
}
if ( days >= 0 && hours >= 0 && minutes >= 0 && seconds >= 0 ) {
window.requestAnimationFrame( count );
}
}
window.requestAnimationFrame( count );
} );
}
// delay execution of carousel script in admin after block is fully loaded
function delayedWlcCountdown() {
let blockLoaded = false;
let blockLoadedInterval = setInterval(() => {
if (document.querySelector('.wlc-countdown')) {
wlcCountdown();
blockLoaded = true;
}
if ( blockLoaded ) {
clearInterval( blockLoadedInterval );
}
}, 500);
}
document.addEventListener( 'DOMContentLoaded', function() {
// in editor execute funtion after server-side-rendered block is fully loaded
if ( document.querySelector('.wp-admin') ) {
delayedWlcCountdown();
// add reloading carousel script after updates
const observer = new MutationObserver( mutations => {
for (const mutation of mutations) {
if (mutation.type == "attributes"
&& mutation.target.classList.contains( 'wlc-countdown') ) {
wlcCountdown();
}
}
});
observer.observe( document.getElementById('editor'), { subtree: true, attributes: true, attributeFilter: [ 'data-props' ] } );
} else {
// else call instantly
wlcCountdown();
}
} );