From 648385b3ce25875ec0f8f6745f7ebb67e568b728 Mon Sep 17 00:00:00 2001 From: Masud Rana Date: Thu, 19 Feb 2026 11:11:41 +0600 Subject: [PATCH] feat: add carousel counter block with current and total slide display --- inc/Plugin.php | 1 + src/blocks/carousel/counter/block.json | 20 ++++++++++ src/blocks/carousel/counter/edit.tsx | 52 ++++++++++++++++++++++++++ src/blocks/carousel/counter/index.ts | 11 ++++++ src/blocks/carousel/counter/save.tsx | 22 +++++++++++ src/blocks/carousel/counter/style.scss | 19 ++++++++++ src/blocks/carousel/types.ts | 1 + src/blocks/carousel/view.ts | 8 ++++ 8 files changed, 134 insertions(+) create mode 100644 src/blocks/carousel/counter/block.json create mode 100644 src/blocks/carousel/counter/edit.tsx create mode 100644 src/blocks/carousel/counter/index.ts create mode 100644 src/blocks/carousel/counter/save.tsx create mode 100644 src/blocks/carousel/counter/style.scss diff --git a/inc/Plugin.php b/inc/Plugin.php index 48196f3..ae6befd 100644 --- a/inc/Plugin.php +++ b/inc/Plugin.php @@ -69,6 +69,7 @@ public function register_blocks(): void { $blocks = [ 'carousel', 'carousel/controls', + 'carousel/counter', 'carousel/dots', 'carousel/viewport', 'carousel/slide', diff --git a/src/blocks/carousel/counter/block.json b/src/blocks/carousel/counter/block.json new file mode 100644 index 0000000..8b8bc49 --- /dev/null +++ b/src/blocks/carousel/counter/block.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "version": "1.0.0", + "name": "carousel-kit/carousel-counter", + "title": "Carousel Counter", + "category": "carousel-kit", + "icon": "editor-ol", + "ancestor": [ + "carousel-kit/carousel" + ], + "description": "Displays current slide number and total slides (e.g., 1 of 3).", + "textdomain": "carousel-kit", + "attributes": {}, + "supports": { + "interactivity": true + }, + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/src/blocks/carousel/counter/edit.tsx b/src/blocks/carousel/counter/edit.tsx new file mode 100644 index 0000000..f42d5ab --- /dev/null +++ b/src/blocks/carousel/counter/edit.tsx @@ -0,0 +1,52 @@ +import { useBlockProps } from '@wordpress/block-editor'; +import { useEffect, useState, useContext } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { EditorCarouselContext } from '../editor-context'; + +export default function Edit() { + const blockProps = useBlockProps( { + className: 'carousel-kit-counter', + } ); + + const { emblaApi } = useContext( EditorCarouselContext ); + const [ totalSlides, setTotalSlides ] = useState( 0 ); + const [ selectedIndex, setSelectedIndex ] = useState( 0 ); + + useEffect( () => { + if ( ! emblaApi ) { + return; + } + + const onInit = () => { + setTotalSlides( emblaApi.slideNodes().length ); + setSelectedIndex( emblaApi.selectedScrollSnap() ); + }; + + const onSelect = () => { + setSelectedIndex( emblaApi.selectedScrollSnap() ); + }; + + emblaApi.on( 'init', onInit ); + emblaApi.on( 'reInit', onInit ); + emblaApi.on( 'select', onSelect ); + + onInit(); // Initial load. + + return () => { + emblaApi.off( 'init', onInit ); + emblaApi.off( 'reInit', onInit ); + emblaApi.off( 'select', onSelect ); + }; + }, [ emblaApi ] ); + + // Calculate current slide (1-based) - fallback to 1 if no slides yet. + const currentSlide = selectedIndex + 1; + + return ( +
+ { currentSlide } + { __( 'of', 'carousel-kit' ) } + { totalSlides || 1 } +
+ ); +} diff --git a/src/blocks/carousel/counter/index.ts b/src/blocks/carousel/counter/index.ts new file mode 100644 index 0000000..90d7f18 --- /dev/null +++ b/src/blocks/carousel/counter/index.ts @@ -0,0 +1,11 @@ +import { registerBlockType, type BlockConfiguration } from '@wordpress/blocks'; +import Edit from './edit'; +import Save from './save'; +import metadata from './block.json'; +import type { CarouselCounterAttributes } from '../types'; +import './style.scss'; + +registerBlockType( metadata as BlockConfiguration, { + edit: Edit, + save: Save, +} ); diff --git a/src/blocks/carousel/counter/save.tsx b/src/blocks/carousel/counter/save.tsx new file mode 100644 index 0000000..1d67d05 --- /dev/null +++ b/src/blocks/carousel/counter/save.tsx @@ -0,0 +1,22 @@ +import { useBlockProps } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; + +export default function Save() { + const blockProps = useBlockProps.save( { + className: 'carousel-kit-counter', + } ); + + return ( +
+ + { __( 'of', 'carousel-kit' ) } + +
+ ); +} diff --git a/src/blocks/carousel/counter/style.scss b/src/blocks/carousel/counter/style.scss new file mode 100644 index 0000000..2b7e61b --- /dev/null +++ b/src/blocks/carousel/counter/style.scss @@ -0,0 +1,19 @@ +/** + * Styles for carousel counter/slide indicator + */ +.carousel-kit-counter { + display: inline-flex; + align-items: center; + gap: var(--carousel-kit-counter-gap, 0.25rem); + font-size: var(--carousel-kit-counter-font-size, inherit); + line-height: 1.5; + color: var(--carousel-kit-counter-color, inherit); +} + +.carousel-kit-counter__separator { + color: var(--carousel-kit-counter-separator-color, inherit); +} + +.carousel-kit-counter__total { + color: var(--carousel-kit-counter-total-color, inherit); +} diff --git a/src/blocks/carousel/types.ts b/src/blocks/carousel/types.ts index fe34731..9cc91fe 100644 --- a/src/blocks/carousel/types.ts +++ b/src/blocks/carousel/types.ts @@ -23,6 +23,7 @@ export type CarouselViewportAttributes = Record; export type CarouselSlideAttributes = Record; export type CarouselControlsAttributes = Record; export type CarouselDotsAttributes = Record; +export type CarouselCounterAttributes = Record; export type CarouselContext = { options: EmblaOptionsType & { diff --git a/src/blocks/carousel/view.ts b/src/blocks/carousel/view.ts index 293aa22..2f7b52b 100644 --- a/src/blocks/carousel/view.ts +++ b/src/blocks/carousel/view.ts @@ -133,6 +133,14 @@ store( 'carousel-kit/carousel', { const index = ( snap?.index || 0 ) + 1; return context.ariaLabelPattern.replace( '%d', index.toString() ); }, + getCurrentSlideNumber: () => { + const context = getContext(); + return ( context.selectedIndex + 1 ).toString(); + }, + getTotalSlides: () => { + const context = getContext(); + return ( context.scrollSnaps?.length || 1 ).toString(); + }, initCarousel: () => { try { const context = getContext();