block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wlc/carousel",
"title": "Carousel",
"category": "widgets",
"icon": "images-alt2",
"description": "A block to display carousel",
"keywords": [
"carousel"
],
"allowedBlocks": [
"wlc/carousel-item"
],
"attributes": {
"innerBlocks": {
"type": "array",
"default": []
},
"type": {
"type": "string",
"default": "slide"
},
"perPage": {
"type": "integer",
"default": 3
},
"perMove": {
"type": "integer",
"default": 1
},
"speed": {
"type": "integer",
"default": "400"
},
"gap": {
"type": "string",
"default": "20px"
},
"autoplay": {
"type": "boolean",
"default": false
},
"interval": {
"type": "string",
"default": "5000"
},
"arrows": {
"type": "boolean",
"default": true
},
"pagination": {
"type": "boolean",
"default": false
},
"pauseOnHover": {
"type": "boolean",
"default": true
},
"tabletBreakpoint": {
"type": "integer",
"default": "1025"
},
"mobileBreakpoint": {
"type": "integer",
"default": "481"
},
"perPageTablet": {
"type": "integer",
"default": null
},
"perPageMobile": {
"type": "integer",
"default": null
}
},
"version": "1.0.0",
"textdomain": "WLC",
"editorStyle": "file:./index.css",
"editorScript": "file:./index.js",
"script": "file:./view.js",
"style": "file:./style-index.css"
}
edit.js
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
import {
useBlockProps,
useInnerBlocksProps,
InspectorControls,
BlockControls,
Inserter,
} from '@wordpress/block-editor';
import {
PanelBody,
Toolbar,
SelectControl,
ToggleControl,
RangeControl,
Button,
__experimentalNumberControl as NumberControl,
__experimentalUnitControl as UnitControl,
} from '@wordpress/components';
import { useEffect, useRef, useState } from 'react';
/**
* External dependencies
*/
import Splide from '@splidejs/splide';
import '@splidejs/splide/dist/css/splide.min.css';
export default function edit( { attributes, setAttributes, clientId } ) {
const { type, perPage, perMove, speed, gap, arrows, pagination, autoplay, interval, pauseOnHover, tabletBreakpoint, mobileBreakpoint, perPageTablet, perPageMobile } = attributes;
const blockProps = useBlockProps();
const splideRef = useRef( null );
const sliderInstance = useRef( null );
const { replaceInnerBlocks } = useDispatch( 'core/block-editor' );
const innerBlocks = useSelect(
( select ) => select( 'core/block-editor' ).getBlocks( clientId ),
[ clientId ]
);
const [ initialSlides, setInitialSlides ] = useState( [] ) ;
// Generate initial slides based on perPage
useEffect( () => {
const slides = [];
for ( let i = 0; i < perPage; i++ ) {
slides.push( [ 'wlc/carousel-item' ] );
}
setInitialSlides( slides );
}, [ perPage ] );
useEffect(() => {
const initializeSplide = () => {
if ( sliderInstance.current ) {
sliderInstance.current.destroy();
}
sliderInstance.current = new Splide( splideRef.current, {
type,
perPage,
perMove,
speed,
gap,
arrows,
pagination,
autoplay,
interval,
pauseOnHover,
breakpoints: {
[tabletBreakpoint]: {
perPage: perPageTablet || perPage,
},
[mobileBreakpoint]: {
perPage: perPageMobile || perPage,
},
}
} ).mount();
};
initializeSplide();
}, [ type, perPage, perMove, speed, gap, arrows, pagination, autoplay, interval, pauseOnHover, tabletBreakpoint, mobileBreakpoint, perPageTablet, perPageMobile, innerBlocks.length ] );
const { children, ...innerBlocksProps } = useInnerBlocksProps(
blockProps,
{
allowedBlocks: [ 'wlc/carousel-item' ],
template: initialSlides,
}
);
const addSlide = () => {
replaceInnerBlocks(
clientId,
[ ...innerBlocks, wp.blocks.createBlock( 'wlc/carousel-item' ) ],
true
);
};
return (
<div { ...blockProps }>
<InspectorControls>
<PanelBody title={ __( 'Carousel Settings', 'WLC' ) }>
<SelectControl
label={ __( 'Type', 'WLC' ) }
value={ type }
options={[
{ value: 'slide', label: __( 'Slide', 'WLC' ) },
{ value: 'loop', label: __( 'Loop', 'WLC' ) },
{ value: 'fade', label: __( 'Fade', 'WLC' ) }
]}
onChange={ ( value ) => setAttributes( { type: value } ) }
/>
<RangeControl
label={ __( 'Slides Per Page', 'WLC' ) }
value={ perPage }
onChange={ ( value ) => setAttributes( { perPage: value } ) }
min={ 1 }
max={ 8 }
/>
<RangeControl
label={ __( 'Slides Per Move', 'WLC' ) }
value={ perMove }
onChange={ ( value) => setAttributes( { perMove: value } ) }
min={ 1 }
max={ 8 }
/>
<NumberControl
label={ __( 'Speed (ms)', 'WLC' ) }
value={ speed }
onChange={ ( value ) => setAttributes( { speed: value } ) }
/>
<UnitControl
label={ __( 'Slides Gap', 'WLC' ) }
value={ gap }
onChange={ ( value ) => setAttributes( { gap: value } ) }
/>
</PanelBody>
<PanelBody title={ __( 'Navigation & Pagination', 'WLC' ) } initialOpen={false}>
<ToggleControl
label={ __ ( 'Show Arrows', 'WLC' ) }
checked={ arrows }
onChange={ ( value ) => setAttributes( { arrows: value } ) }
/>
<ToggleControl
label={ __( 'Show Pagination', 'WLC' ) }
checked={ pagination }
onChange={ ( value ) => setAttributes( { pagination: value } ) }
/>
</PanelBody>
<PanelBody title={ __( 'Autoplay', 'WLC' ) } initialOpen={false}>
<ToggleControl
label={ __( 'Autoplay', 'WLC' ) }
checked={ autoplay }
onChange={ ( value ) => setAttributes( { autoplay: value } ) }
/>
{ autoplay && (
<>
<NumberControl
label={ __( 'Interval (ms)', 'WLC' ) }
value={ interval }
onChange={ ( value ) => setAttributes( { interval: value } ) }
/>
<ToggleControl
label={ __( 'Pause on Hover', 'WLC' ) }
checked={ pauseOnHover }
onChange={ ( value ) => setAttributes( { pauseOnHover: value } ) }
/>
</>
) }
</PanelBody>
<PanelBody title={ __( 'Breakpoints', 'WLC' ) } initialOpen={false}>
<NumberControl
label={ __( 'Tablet Breakpoint (px)', 'WLC' ) }
value={ tabletBreakpoint }
onChange={ ( value ) => setAttributes( { tabletBreakpoint: value } ) }
/>
<RangeControl
label={ __( 'Slides Per Page (Tablet)', 'WLC') }
value={ perPageTablet }
onChange={ ( value ) => setAttributes( { perPageTablet: value } ) }
min={ 1 }
max={ 8 }
/>
<NumberControl
label={ __( 'Mobile Breakpoint (px)', 'WLC' ) }
value={ mobileBreakpoint }
onChange={ ( value ) => setAttributes( { mobileBreakpoint: value } ) }
/>
<RangeControl
label={ __( 'Slides Per Page (Mobile)', 'WLC' ) }
value={ perPageMobile }
onChange={ ( value) => setAttributes( { perPageMobile: value } ) }
min={ 1 }
max={ 8 }
/>
</PanelBody>
</InspectorControls>
<BlockControls>
<Toolbar label={ __( 'Options', 'WLC' ) }>
<Button
onClick={ addSlide }
label={ __( 'Add slide', 'WLC' ) }
icon="plus"
/>
</Toolbar>
</BlockControls>
<div
ref={ splideRef }
className="splide"
>
<div className="splide__track">
<ul { ...innerBlocksProps } className="splide__list">
{ children }
</ul>
</div>
{ arrows && (
<div className="splide__arrows splide__arrows--ltr"></div>
) }
{ pagination && (
<ul className="splide__pagination splide__pagination--ltr" role="tablist" aria-label={ __( 'Select a slide to show', 'WLC' ) }></ul>
) }
</div>
</div>
);
}
editor.scss
@import '@splidejs/splide/dist/css/splide-core.min.css';
.wp-block-wlc-carousel .splide__slide .block-list-appender {
position: relative;
}
index.js
/**
* WordPress dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import './editor.scss';
import './style.scss';
import edit from './edit';
import save from './save';
import metadata from './block.json';
registerBlockType( metadata.name, {
edit,
save,
} );
render.php
<?php
/**
* Renders the html of posts carousel block
*
* @package wlc/carousel-block
*/
$posts_type = $attributes['postType'] ? $attributes['postType'] : 'post';
$number_posts = $attributes['numberPosts'] ? ( (int) $attributes['numberPosts'] ) : 5;
$delay = $attributes['delay'] ? ( (int) $attributes['delay'] * 1000 ) : 5000;
$arrows = (bool) $attributes['showArrows'];
$nav = (bool) $attributes['showNavigation'];
$args = array(
'post_type' => $posts_type,
'post_status' => 'publish',
'nopaging' => true,
'posts_per_page' => $number_posts,
'ignore_sticky_posts' => false,
);
$query = new \WP_Query( $args );
if ( $query->have_posts() ) :
$settings = wp_json_encode(
array(
'delay' => $delay,
'arrows' => $arrows,
'nav' => $nav,
)
);
$block_attrs = get_block_wrapper_attributes();
?>
<div class="wlc-carousel" data-settings="<?php echo esc_attr( $settings ); ?>" <?php echo wp_kses_data( $block_attrs ); ?>>
<div class="wlc-carousel__slider splide">
<div class="splide__track">
<ul class="splide__list">
<?php
while ( $query->have_posts() ) {
$query->the_post();
?>
<li class="splide__slide">
<div class="wlc-carousel__content">
<h2 class="wlc-carousel__title"><?php the_title(); ?></h2>
<span class="wlc-carousel__date"><?php the_date(); ?></span>
<div class="wlc-carousel__excerpt">
<?php the_excerpt(); ?>
</div>
<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button">
<a href="<?php the_permalink(); ?>" class="wp-block-button__link wp-element-button"><?php echo esc_html__( 'Czytaj więcej', 'WLC' ); ?></a>
</div>
</div>
</div>
<div class="wlc-carousel__thumbnail">
<?php the_post_thumbnail(); ?>
</div>
</li>
<?php
}
?>
</ul>
</div>
<?php
if ( $arrows ) :
?>
<div class="splide__arrows splide__arrows--ltr"></div>
<?php
endif;
?>
</div>
<?php
if ( $nav ) :
?>
<div class="wlc-carousel__slider-nav splide">
<div class="splide__track">
<ul class="splide__list">
<?php
while ( $query->have_posts() ) {
$query->the_post();
?>
<li class="splide__slide">
<div class="wlc-carousel-nav-content">
<p class="wlc-carousel-nav-title"><?php the_title(); ?></p>
</div>
</li>
<?php
}
?>
</ul>
</div>
</div>
<?php
endif;
?>
</div>
<?php
endif;
wp_reset_postdata();
?>
save.js
/**
* WordPress dependencies
*/
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const { type, perPage, perMove, speed, gap, arrows, pagination, autoplay, interval, pauseOnHover, tabletBreakpoint, mobileBreakpoint, perPageTablet, perPageMobile } = attributes;
const blockProps = useBlockProps.save( {
className: 'splide',
'data-type': type,
'data-per-page': perPage,
'data-per-move': perMove,
'data-speed': speed,
'data-gap': gap,
'data-arrows': arrows,
'data-pagination': pagination,
'data-autoplay': autoplay,
'data-interval': interval,
'data-pause-on-hover': pauseOnHover,
'data-tablet-breakpoint': tabletBreakpoint,
'data-mobile-breakpoint': mobileBreakpoint,
'data-per-page-tablet': perPageTablet,
'data-per-page-mobile': perPageMobile,
} );
return (
<div className="wlc-carousel">
<div { ...blockProps }>
<div className="splide__track">
<ul className="splide__list">
<InnerBlocks.Content />
</ul>
</div>
</div>
</div>
);
}
style.css
@import '@splidejs/splide/dist/css/splide-core.min.css';
.wlc-carousel {
@apply px-4;
}
.wlc-carousel__slider {
@apply relative;
& .splide__slide {
@apply grid max-lg:grid-cols-1 lg:grid-cols-[1fr_2fr] max-lg:grid-rows-[auto_auto] lg:grid-rows-1 gap-4;
}
& .wlc-carousel__content {
@apply grid grid-rows-[auto_auto_1fr_auto] col-start-1 max-lg:row-start-2 lg:row-start-1;
.wp-element-button {
@apply block;
}
}
& .wlc-carousel__thumbnail {
@apply max-lg:col-start-1 lg:col-start-2 row-start-1;
.wp-post-image {
@apply w-full h-full object-cover rounded-2xl overflow-hidden;
}
}
& .splide__arrows {
@apply w-full absolute top-[50%] translate-y-[-50%];
.splide__arrow--prev {
@apply absolute right-full scale-x-[-1];
}
.splide__arrow--next {
@apply absolute left-full;
}
}
}
.wlc-carousel__slider-nav {
@apply mt-4;
.wlc-carousel-nav-content {
@apply p-4 border border-[#ccc] rounded-2xl;
p {
@apply p-0 m-0;
}
}
}
style.scss
@import '@splidejs/splide/dist/css/splide-core.min.css';
.wlc-carousel {
@apply px-4;
& .splide__arrows {
@apply w-full absolute top-[50%] translate-y-[-50%];
.splide__arrow--prev {
@apply absolute right-full scale-x-[-1];
}
.splide__arrow--next {
@apply absolute left-full;
}
.splide__arrow:disabled {
@apply opacity-25 cursor-not-allowed;
}
}
}
view.js
import Splide from '@splidejs/splide';
document.addEventListener('DOMContentLoaded', function () {
const sliders = document.querySelectorAll('.wlc-carousel');
sliders.forEach(slider => {
const splideElement = slider.querySelector('.splide');
const options = {
type: splideElement.getAttribute('data-type'),
perPage: parseInt(splideElement.getAttribute('data-per-page'), 10),
perMove: parseInt(splideElement.getAttribute('data-per-move'), 10),
speed: parseInt(splideElement.getAttribute('data-speed'), 10),
gap: splideElement.getAttribute('data-gap'),
arrows: splideElement.getAttribute('data-arrows') === 'true',
pagination: splideElement.getAttribute('data-pagination') === 'true',
autoplay: splideElement.getAttribute('data-autoplay') === 'true',
interval: parseInt(splideElement.getAttribute('data-interval'), 10),
pauseOnHover: splideElement.getAttribute('data-pause-on-hover') === 'true',
breakpoints: {
[parseInt(splideElement.getAttribute('data-tablet-breakpoint'), 10)]: {
perPage: parseInt(splideElement.getAttribute('data-per-page-tablet'), 10) || parseInt(splideElement.getAttribute('data-per-page'), 10),
},
[parseInt(splideElement.getAttribute('data-mobile-breakpoint'), 10)]: {
perPage: parseInt(splideElement.getAttribute('data-per-page-mobile'), 10) || parseInt(splideElement.getAttribute('data-per-page'), 10),
},
}
};
new Splide(splideElement, options).mount();
});
});