over than
50
Customers in this year
over than
120
orders per day
20
years in industry
Tekst nad blokiem
cena: 1 234,56zł
tekst pod blokiem
block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wlc/number-counter",
"title": "Number Counter",
"category": "theme",
"icon": "dashboard",
"description": "Number Counter",
"keywords": [ "number", "counter" ],
"version": "1.0.0",
"textdomain": "WLC",
"supports": {
"renaming": true,
"interactivity": true,
"align": true,
"anchor": true,
"color": {
"gradients": true
},
"shadow": true,
"spacing": {
"margin": true,
"padding": true
},
"typography": true
},
"attributes": {
"startValue": {
"type": "string",
"default": "0"
},
"endValue": {
"type": "string",
"source": "attribute",
"attribute": "data-raw-value",
"selector": "span.value",
"default": "0"
},
"prefix": {
"type": "string",
"source": "text",
"selector": "span.prefix"
},
"suffix": {
"type": "string",
"source": "text",
"selector": "span.suffix"
},
"textAbove": {
"type": "string",
"source": "text",
"selector": "p.text-above"
},
"textBelow": {
"type": "string",
"source": "text",
"selector": "p.text-below"
},
"duration": {
"type" : "string",
"default": "5000"
},
"easing": {
"enum": [ "linear", "easeIn", "easeInOut", "easeOut" ],
"default": "linear"
},
"thousandsSeparator": {
"enum": [".", ",", " ", "" ],
"default": ""
},
"decimalsSeparator": {
"enum": [",", "." ],
"default": "."
},
"textAlign": {
"type": "string",
"default": "center"
},
"styles": {
"type": "object",
"default": {
"textAbove": {
"fontSize": "1rem",
"lineHeight": "1.5",
"fontStyle": "normal"
},
"prefix": {
"fontSize": "2rem",
"lineHeight": "1.5",
"fontStyle": "normal"
},
"value": {
"fontSize": "2rem",
"lineHeight": "1.5",
"fontWeight": "700",
"fontStyle": "normal"
},
"suffix": {
"fontSize": "2rem",
"lineHeight": "1.5",
"fontStyle": "normal"
},
"textBelow": {
"fontSize": "1rem",
"lineHeight": "1.5",
"fontStyle": "normal"
}
}
}
},
"editorScript": "file:./index.js",
"viewScriptModule": "file:./view.js",
"style": "file:./style-index.css"
}edit.js
import {
useBlockProps,
InspectorControls,
BlockControls,
AlignmentControl,
RichText,
FontSizePicker,
} from '@wordpress/block-editor'
import {
PanelBody,
TextControl,
RadioControl,
SelectControl,
ColorPalette,
Dropdown,
Button,
ColorIndicator,
} from '@wordpress/components'
import { select } from '@wordpress/data';
import { __ } from '@wordpress/i18n'
import { addSeparators } from './helpers'
const edit = ( { attributes, setAttributes } ) => {
const {
startValue, endValue,
prefix, suffix,
textAbove, textBelow,
thousandsSeparator, decimalsSeparator,
duration, easing,
textAlign,
styles,
} = attributes;
const blockProps = useBlockProps({ className: 'wlc-number-counter'});
const globalSettings = select( "core/editor" ).getEditorSettings();
const set = ( attr ) => {
const prop = Object.keys(attr).pop();
return value => setAttributes( { [prop]: value } )
}
const displayedValue = addSeparators( endValue, thousandsSeparator, decimalsSeparator);
const SettingsContainer = props => {
const style = styles[props.el]
const set = (prop,value) => {
style[prop] = value
setAttributes( { styles: { ...styles, style } } )
}
return (
<>
<FontSizePicker
value={ style.fontSize }
onChange={ value => set('fontSize', value ) }
/>
<TextControl
label={ __('Line Height', 'WLC' ) }
value={ style.lineHeight }
type="number"
step="0.1"
onChange={ value => set('lineHeight', value ) }
/>
<SelectControl
label={ __('Font weight', 'WLC' ) }
value={ style.fontWeight }
options={ [
{ label: __('Thin', 'WLC'), value: 100},
{ label: __('Extra Light', 'WLC'), value: 200},
{ label: __('Light', 'WLC'), value: 300},
{ label: __('Regular', 'WLC'), value: 400},
{ label: __('Medium', 'WLC'), value: 500},
{ label: __('Semi Bold', 'WLC'), value: 600},
{ label: __('Bold', 'WLC'), value: 700},
{ label: __('Extra Bold', 'WLC'), value: 800},
] }
onChange={ value => set('fontWeight', value ) }
/>
<RadioControl
label={ __('Font style', 'WLC' ) }
selected={ style.fontStyle }
options = { [
{ label: __('Normal', 'WLC' ), value: "normal" },
{ label: __('Italic', 'WLC' ), value: "italic" },
]}
onChange={ value => set('fontStyle', value ) }
/>
</>
)
}
return (
<>
<InspectorControls>
<PanelBody title={ __('Values', 'WLC' ) }>
<TextControl
label={ __('Start Value', 'WLC' ) }
type="number"
value={ startValue }
onChange={ set({ startValue }) }
/>
<TextControl
label={ __('End Value', 'WLC' ) }
type="number"
value={ endValue }
onChange={ set({ endValue }) }
/>
<TextControl
label={ __('Prefix', 'WLC' ) }
value={ prefix }
onChange={ set({ prefix }) }
/>
<TextControl
label={ __('Suffix', 'WLC' ) }
value={ suffix }
onChange={ set({ suffix }) }
/>
</PanelBody>
<PanelBody title={ __('Number formatting', 'WLC' ) } initialOpen={ false }>
<RadioControl
label={ __('Displayed decimal separator', 'WLC' ) }
selected={ decimalsSeparator }
options = { [
{ label: __('Dot', 'WLC' ), value: "." },
{ label: __('Comma', 'WLC' ), value: "," }
]}
onChange={ set({ decimalsSeparator }) }
/>
<RadioControl
label={ __('Displayed thousands separator', 'WLC' ) }
selected={ thousandsSeparator }
options = { [
{ label: __('Dot', 'WLC' ), value: "." },
{ label: __('Comma', 'WLC' ), value: "," },
{ label: __('Space', 'WLC' ), value: " " },
{ label: __('None', 'WLC' ), value: "" }
]}
onChange={ set({ thousandsSeparator }) }
/>
</PanelBody>
<PanelBody title={ __('Animation', 'WLC' ) } initialOpen={ false }>
<TextControl
label={ __('Duration', 'WLC' ) }
type="number"
value={ duration }
onChange={ set({ duration }) }
/>
<RadioControl
label={ __('Easing', 'WLC' ) }
selected={ easing }
options = { [
{ label: __('Linear', 'WLC' ), value: "linear" },
{ label: __('EaseIn', 'WLC' ), value: "easeIn" },
{ label: __('EaseInOut', 'WLC' ), value: "easeInOut" },
{ label: __('EaseOut', 'WLC' ), value: "easeOut" }
]}
onChange={ set({ easing }) }
/>
</PanelBody>
</InspectorControls>
<InspectorControls group="styles">
<PanelBody title={ __('Text above value', 'WLC' ) } initialOpen={ false }>
<SettingsContainer el="textAbove"/>
<Dropdown
popoverProps={ { placement: 'left-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
onClick={ onToggle }
aria-expanded={ isOpen }
>
<ColorIndicator colorValue={styles.textAbove.color} /> { __( 'Select color', 'WLC' ) }
</Button>
) }
renderContent={ () =>
<ColorPalette
value={ styles.textAbove.color }
colors= { globalSettings.colors }
onChange={ value => setAttributes( {
styles: { ...styles, textAbove: { ...styles.textAbove, color: value } }
} ) }
enableAlpha
clearable
defaultValue="#000"
/>
}
/>
</PanelBody>
<PanelBody title={ __('Value Prefix', 'WLC' ) } initialOpen={ false }>
<SettingsContainer el="prefix"/>
<Dropdown
popoverProps={ { placement: 'left-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
onClick={ onToggle }
aria-expanded={ isOpen }
>
<ColorIndicator colorValue={styles.prefix.color} /> { __( 'Select color', 'WLC' ) }
</Button>
) }
renderContent={ () =>
<ColorPalette
value={ styles.prefix.color }
colors= { globalSettings.colors }
onChange={ value => setAttributes( {
styles: { ...styles, prefix: { ...styles.prefix, color: value } }
} ) }
enableAlpha
clearable
defaultValue="#000"
/>
}
/>
</PanelBody>
<PanelBody title={ __('Value', 'WLC' ) } initialOpen={ false }>
<SettingsContainer el="value"/>
<Dropdown
popoverProps={ { placement: 'left-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
onClick={ onToggle }
aria-expanded={ isOpen }
>
<ColorIndicator colorValue={styles.value.color} /> { __( 'Select color', 'WLC' ) }
</Button>
) }
renderContent={ () =>
<ColorPalette
value={ styles.value.color }
colors= { globalSettings.colors }
onChange={ value => setAttributes( {
styles: { ...styles, value: { ...styles.value, color: value } }
} ) }
enableAlpha
clearable
defaultValue="#000"
/>
}
/>
</PanelBody>
<PanelBody title={ __('Value Suffix', 'WLC' ) } initialOpen={ false }>
<SettingsContainer el="suffix"/>
<Dropdown
popoverProps={ { placement: 'left-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
onClick={ onToggle }
aria-expanded={ isOpen }
>
<ColorIndicator colorValue={styles.suffix.color} /> { __( 'Select color', 'WLC' ) }
</Button>
) }
renderContent={ () =>
<ColorPalette
value={ styles.suffix.color }
colors= { globalSettings.colors }
onChange={ value => setAttributes( {
styles: { ...styles, suffix: { ...styles.suffix, color: value } }
} ) }
enableAlpha
clearable
defaultValue="#000"
/>
}
/>
</PanelBody>
<PanelBody title={ __('Text below value', 'WLC' ) } initialOpen={ false }>
<SettingsContainer el="textBelow"/>
<Dropdown
popoverProps={ { placement: 'left-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
onClick={ onToggle }
aria-expanded={ isOpen }
>
<ColorIndicator colorValue={styles.textBelow.color} /> { __( 'Select color', 'WLC' ) }
</Button>
) }
renderContent={ () =>
<ColorPalette
value={ styles.textBelow.color }
colors= { globalSettings.colors }
onChange={ value => setAttributes( {
styles: { ...styles, textBelow: { ...styles.textBelow, color: value } }
} ) }
enableAlpha
clearable
defaultValue="#000"
/>
}
/>
</PanelBody>
</InspectorControls>
<BlockControls>
<AlignmentControl
value={ textAlign }
onChange={ set({ textAlign }) }
/>
</BlockControls>
<div
{ ...blockProps }
>
<RichText
tagName="p"
className="text-above"
style={ { ...styles.textAbove, textAlign } }
value={ textAbove }
onChange={ set({ textAbove }) }
placeholder={ __('Text Above', 'WLC' ) }
/>
<p style={ { textAlign } }>
<span className="prefix" style={ { ...styles.prefix, textAlign } }>{ prefix }</span>
<span className="value" style={ {...styles.value} }>{ displayedValue }</span>
<span className="suffix" style={ { ...styles.suffix, textAlign } }>{ suffix }</span>
</p>
<RichText
tagName="p"
className="text-below"
style={ { ...styles.textBelow, textAlign } }
value={ textBelow }
onChange={ set({ textBelow }) }
placeholder={ __('Text Below', 'WLC' ) }
/>
</div>
</>
)
}
export default edit;helpers.js
export const addSeparators = ( num, thousandsSep, decimalsSep ) => {
const numStr = num.toString();
const parts = numStr.split(".");
const regex = /(\d)(?=(\d{3})+(?!\d))/g;
parts[0] = parts[0].replace(regex, `$1${thousandsSep}`)
return parts.join(decimalsSep);
}
const easings = {
linear: time => time,
easeIn: time => Math.pow(time,3),
easeInOut: time => time < 0.5 ? 4 * Math.pow(time,3) : 1 - Math.pow(-2 * time + 2, 3) / 2,
easeOut: time => 1 - Math.pow(1 - time, 3),
}
export const easing = type => easings[type];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, RichText } from '@wordpress/block-editor'
import { __ } from '@wordpress/i18n'
import { addSeparators } from './helpers'
const save = ( { attributes } ) => {
const {
startValue, endValue,
prefix, suffix,
thousandsSeparator, decimalsSeparator,
textAbove, textBelow,
duration, easing,
textAlign,
styles
} = attributes;
const blockProps = useBlockProps.save({ className: 'wlc-number-counter'});
const context = JSON.stringify( {
start: parseFloat( startValue ),
end: parseFloat( endValue ),
thousandsSeparator,
decimalsSeparator,
duration: parseInt( duration ),
easing,
isAnimating: false,
} );
const displayedValue = addSeparators( endValue, thousandsSeparator, decimalsSeparator);
return (
<div
{ ...blockProps }
data-wp-interactive="wlc/number-counter"
data-wp-run="callbacks.animateNumber"
data-wp-context={ context }
>
<RichText.Content tagName="p" className="text-above" value={ textAbove } style={ {...styles.textAbove, textAlign } }/>
<p style={ { textAlign } }>
<span className="prefix" style={ {...styles.prefix, textAlign } }>{ prefix }</span>
<span className="value" data-raw-value={ endValue } style={ {...styles.value } }>{ displayedValue }</span>
<span className="suffix" style={ {...styles.suffix, textAlign } }>{ suffix }</span>
</p>
<RichText.Content tagName="p" className="text-below" value={ textBelow } style={ {...styles.textBelow, textAlign } }/>
</div>
)
}
export default save;style.css
.wlc-number-counter {
& > p {
@apply m-0 p-0;
}
}view.js
import { getElement, getContext, store, useState, useEffect } from '@wordpress/interactivity'
import { addSeparators, easing } from './helpers';
const useInView = () => {
const [ inView, setInView ] = useState(false);
useEffect( () => {
const { ref } = getElement();
const observer = new IntersectionObserver( ( [ entry ] ) => {
setInView( entry.isIntersecting );
} );
observer.observe( ref );
return () => ref && observer.unobserve( ref );
}, []);
return inView;
}
const animate = () => {
const context = getContext();
context.isAnimating = true;
const { ref } = getElement();
const duration = context.duration;
const start = performance.now();
const range = context.end - context.start;
const timing = easing(context.easing);
const nextFrame = () => {
const now = performance.now();
const delta = Math.min((now - start) / duration, 1);
const current = context.start + ( timing(delta) * range ) ;
const value = addSeparators(current.toFixed(2), context.thousandsSeparator, context.decimalsSeparator );
ref.querySelector('.value').textContent = value;
if ( delta < 1 ) {
window.requestAnimationFrame(nextFrame);
}
}
window.requestAnimationFrame(nextFrame);
}
store( 'wlc/number-counter', {
callbacks: {
animateNumber: () => {
const inView = useInView();
useEffect( () => {
const context = getContext();
if ( inView && !context.isAnimating ) {
animate();
}
})
}
}
} )