Skip to content

Commit 689dea5

Browse files
authored
Merge pull request #54 from rtCamp/feat/setup-wizard
feat: carousel setup wizard, nav+dots auto-insert, and slide reInit
2 parents 8281bc8 + 7c9016e commit 689dea5

6 files changed

Lines changed: 247 additions & 56 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
## [1.0.2](https://github.com/rtCamp/carousel-system-interactivity-api/compare/v1.0.1...v1.0.2) (2026-02-23)
2+
3+
4+
### Bug Fixes
5+
6+
* **demo:** 4 slides per view ([e13817c](https://github.com/rtCamp/carousel-system-interactivity-api/commit/e13817c8e49e0e071580efbf95ff4c77d677e114))
7+
* replace PNG images with optimized WEBP format ([eb60082](https://github.com/rtCamp/carousel-system-interactivity-api/commit/eb6008251039ea700262c63db89a266072ee6c3f))
8+
* replace urls with webp alts ([000f894](https://github.com/rtCamp/carousel-system-interactivity-api/commit/000f894a9818bd8181977cb038231e0ea3d82386))
9+
10+
### Features
11+
12+
* setup wizard styles ([0be0bf7](https://github.com/rtCamp/carousel-system-interactivity-api/commit/0be0bf7cde8c9035cca1086dce7dce005e0d39b2))
13+
* slide appender and setup ([a42331d](https://github.com/rtCamp/carousel-system-interactivity-api/commit/a42331d10b225379408ddf8c0649e83484496a1e))
14+
15+
116
# [1.0.1](https://github.com/rtCamp/carousel-system-interactivity-api/compare/1.0.0...1.0.1) (2026-02-16)
217

318
### Bug Fixes

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "carousel-kit",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "Carousel block using Embla and WordPress Interactivity API",
55
"author": "rtCamp",
66
"private": true,
@@ -63,4 +63,4 @@
6363
"react-dom": "^18.3.1",
6464
"webpack-dev-server": ">=5.2.1"
6565
}
66-
}
66+
}

src/blocks/carousel/edit.tsx

Lines changed: 146 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
useInnerBlocksProps,
55
InspectorControls,
66
InspectorAdvancedControls,
7+
BlockControls,
78
} from '@wordpress/block-editor';
89
import {
910
PanelBody,
@@ -13,25 +14,26 @@ import {
1314
BaseControl,
1415
TextControl,
1516
RangeControl,
17+
Placeholder,
18+
Button,
19+
ToolbarButton,
1620
} from '@wordpress/components';
17-
import { useSelect } from '@wordpress/data';
18-
import { useState, useMemo } from '@wordpress/element';
21+
import { plus } from '@wordpress/icons';
22+
import { useSelect, useDispatch } from '@wordpress/data';
23+
import { useState, useMemo, useCallback } from '@wordpress/element';
24+
import { createBlock, type BlockConfiguration } from '@wordpress/blocks';
1925
import type { CarouselAttributes } from './types';
20-
import type { BlockConfiguration, Template } from '@wordpress/blocks';
2126
import { EditorCarouselContext } from './editor-context';
2227
import type { EmblaCarouselType } from 'embla-carousel';
2328

24-
const TEMPLATE: Template[] = [
25-
[ 'carousel-kit/carousel-viewport', {} ],
26-
[ 'carousel-kit/carousel-controls', {} ],
27-
];
28-
2929
export default function Edit( {
3030
attributes,
3131
setAttributes,
32+
clientId,
3233
}: {
3334
attributes: CarouselAttributes;
3435
setAttributes: ( attrs: Partial<CarouselAttributes> ) => void;
36+
clientId: string;
3537
} ) {
3638
const {
3739
loop,
@@ -54,12 +56,37 @@ export default function Edit( {
5456
const [ canScrollPrev, setCanScrollPrev ] = useState( false );
5557
const [ canScrollNext, setCanScrollNext ] = useState( false );
5658

57-
// Fetch all registered block types for suggestions
58-
const blockTypes = useSelect( ( select ) => {
59-
return (
59+
const { replaceInnerBlocks, insertBlock } = useDispatch( 'core/block-editor' );
60+
61+
const hasInnerBlocks = useSelect(
62+
( select ) =>
6063
// eslint-disable-next-line @typescript-eslint/no-explicit-any
61-
select( 'core/blocks' ) as any
62-
).getBlockTypes() as BlockConfiguration[];
64+
( select( 'core/block-editor' ) as any ).getBlockCount( clientId ) > 0,
65+
[ clientId ],
66+
);
67+
68+
const viewportClientId = useSelect(
69+
( select ) => {
70+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
71+
const innerBlocks = ( select( 'core/block-editor' ) as any ).getBlocks( clientId ) as Array<{ name: string; clientId: string }>;
72+
return innerBlocks.find( ( b ) => b.name === 'carousel-kit/carousel-viewport' )?.clientId;
73+
},
74+
[ clientId ],
75+
);
76+
77+
const addSlide = useCallback( () => {
78+
if ( ! viewportClientId ) {
79+
return;
80+
}
81+
insertBlock( createBlock( 'carousel-kit/carousel-slide' ), undefined, viewportClientId );
82+
}, [ insertBlock, viewportClientId ] );
83+
84+
const showSetup = ! hasInnerBlocks;
85+
86+
// Fetch registered block types for the allowed-blocks token field
87+
const blockTypes = useSelect( ( select ) => {
88+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
89+
return ( select( 'core/blocks' ) as any ).getBlockTypes() as BlockConfiguration[];
6390
}, [] );
6491

6592
const suggestions = blockTypes?.map( ( block ) => block.name ) || [];
@@ -75,11 +102,8 @@ export default function Edit( {
75102
} as React.CSSProperties,
76103
} );
77104

78-
const innerBlocksProps = useInnerBlocksProps( blockProps, {
79-
template: TEMPLATE,
80-
} );
105+
const innerBlocksProps = useInnerBlocksProps( blockProps, {} );
81106

82-
// Memoize carouselOptions separately to prevent excessive viewport reinitializations
83107
const carouselOptions = useMemo(
84108
() => ( {
85109
loop,
@@ -94,8 +118,6 @@ export default function Edit( {
94118
[ loop, dragFree, carouselAlign, containScroll, direction, axis, height, slidesToScroll ],
95119
);
96120

97-
// Memoize the context value to prevent infinite re-renders in children
98-
// Note: setState functions are stable and don't need to be in dependencies
99121
const contextValue = useMemo(
100122
() => ( {
101123
emblaApi,
@@ -117,8 +139,49 @@ export default function Edit( {
117139
],
118140
);
119141

120-
return (
121-
<EditorCarouselContext.Provider value={ contextValue }>
142+
const createNavGroup = () =>
143+
createBlock(
144+
'core/group',
145+
{
146+
layout: {
147+
type: 'flex',
148+
flexWrap: 'nowrap',
149+
justifyContent: 'space-between',
150+
},
151+
},
152+
[
153+
createBlock( 'carousel-kit/carousel-controls', {} ),
154+
createBlock( 'carousel-kit/carousel-dots', {} ),
155+
],
156+
);
157+
158+
const handleSetup = ( slideCount: number ) => {
159+
const slides = Array.from( { length: slideCount }, () =>
160+
createBlock( 'carousel-kit/carousel-slide', {}, [
161+
createBlock( 'core/paragraph', {} ),
162+
] ),
163+
);
164+
165+
replaceInnerBlocks(
166+
clientId,
167+
[ createBlock( 'carousel-kit/carousel-viewport', {}, slides ), createNavGroup() ],
168+
false,
169+
);
170+
};
171+
172+
/**
173+
* Skip — still creates the correct structure, just without slides.
174+
*/
175+
const handleSkip = () => {
176+
replaceInnerBlocks(
177+
clientId,
178+
[ createBlock( 'carousel-kit/carousel-viewport', {} ), createNavGroup() ],
179+
false,
180+
);
181+
};
182+
183+
const inspectorControls = (
184+
<>
122185
<InspectorControls>
123186
<PanelBody title={ __( 'Carousel Settings', 'carousel-kit' ) }>
124187
<ToggleControl
@@ -145,9 +208,7 @@ export default function Edit( {
145208
{ label: __( 'End', 'carousel-kit' ), value: 'end' },
146209
] }
147210
onChange={ ( value ) =>
148-
setAttributes( {
149-
carouselAlign: value as CarouselAttributes['carouselAlign'],
150-
} )
211+
setAttributes( { carouselAlign: value as CarouselAttributes[ 'carouselAlign' ] } )
151212
}
152213
/>
153214
<SelectControl
@@ -159,9 +220,7 @@ export default function Edit( {
159220
{ label: __( 'None', 'carousel-kit' ), value: '' },
160221
] }
161222
onChange={ ( value ) =>
162-
setAttributes( {
163-
containScroll: value as CarouselAttributes['containScroll'],
164-
} )
223+
setAttributes( { containScroll: value as CarouselAttributes[ 'containScroll' ] } )
165224
}
166225
help={ __(
167226
'Prevents excess scrolling at the beginning or end.',
@@ -171,8 +230,13 @@ export default function Edit( {
171230
<ToggleControl
172231
label={ __( 'Scroll Auto', 'carousel-kit' ) }
173232
checked={ slidesToScroll === 'auto' }
174-
onChange={ ( isAuto ) => setAttributes( { slidesToScroll: isAuto ? 'auto' : '1' } ) }
175-
help={ __( 'Scrolls the number of slides currently visible in the viewport.', 'carousel-kit' ) }
233+
onChange={ ( isAuto ) =>
234+
setAttributes( { slidesToScroll: isAuto ? 'auto' : '1' } )
235+
}
236+
help={ __(
237+
'Scrolls the number of slides currently visible in the viewport.',
238+
'carousel-kit',
239+
) }
176240
/>
177241
{ slidesToScroll !== 'auto' && (
178242
<RangeControl
@@ -193,9 +257,7 @@ export default function Edit( {
193257
{ label: __( 'Right to Left (RTL)', 'carousel-kit' ), value: 'rtl' },
194258
] }
195259
onChange={ ( value ) =>
196-
setAttributes( {
197-
direction: value as CarouselAttributes['direction'],
198-
} )
260+
setAttributes( { direction: value as CarouselAttributes[ 'direction' ] } )
199261
}
200262
help={ __(
201263
'Choose content direction. RTL is typically used for Arabic, Hebrew, and other right-to-left languages.',
@@ -210,9 +272,7 @@ export default function Edit( {
210272
{ label: __( 'Vertical', 'carousel-kit' ), value: 'y' },
211273
] }
212274
onChange={ ( value ) =>
213-
setAttributes( {
214-
axis: value as CarouselAttributes['axis'],
215-
} )
275+
setAttributes( { axis: value as CarouselAttributes[ 'axis' ] } )
216276
}
217277
/>
218278
{ axis === 'y' && (
@@ -322,6 +382,57 @@ export default function Edit( {
322382
/>
323383
</PanelBody>
324384
</InspectorControls>
385+
</>
386+
);
387+
388+
if ( showSetup ) {
389+
return (
390+
<EditorCarouselContext.Provider value={ contextValue }>
391+
{ inspectorControls }
392+
<div { ...blockProps }>
393+
<Placeholder
394+
icon="columns"
395+
label={ __( 'Carousel', 'carousel-kit' ) }
396+
instructions={ __( 'How many slides would you like to start with?', 'carousel-kit' ) }
397+
className="carousel-kit-setup"
398+
>
399+
<div className="carousel-kit-setup__options">
400+
{ [ 1, 2, 3, 4 ].map( ( count ) => (
401+
<Button
402+
key={ count }
403+
variant="secondary"
404+
className="carousel-kit-setup__option"
405+
onClick={ () => handleSetup( count ) }
406+
>
407+
{ count === 1
408+
? __( '1 Slide', 'carousel-kit' )
409+
: `${ count } ${ __( 'Slides', 'carousel-kit' ) }` }
410+
</Button>
411+
) ) }
412+
</div>
413+
<Button
414+
variant="link"
415+
className="carousel-kit-setup__skip"
416+
onClick={ handleSkip }
417+
>
418+
{ __( 'Skip', 'carousel-kit' ) }
419+
</Button>
420+
</Placeholder>
421+
</div>
422+
</EditorCarouselContext.Provider>
423+
);
424+
}
425+
426+
return (
427+
<EditorCarouselContext.Provider value={ contextValue }>
428+
<BlockControls>
429+
<ToolbarButton
430+
icon={ plus }
431+
label={ __( 'Add Slide', 'carousel-kit' ) }
432+
onClick={ addSlide }
433+
/>
434+
</BlockControls>
435+
{ inspectorControls }
325436
<div { ...innerBlocksProps } />
326437
</EditorCarouselContext.Provider>
327438
);

src/blocks/carousel/editor.scss

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,47 @@
11
/**
22
* Editor-only styles for Carousel
33
*/
4+
5+
// ── Setup chooser ────────────────────────────────────────────────────────────
6+
.carousel-kit-setup {
7+
.carousel-kit-setup__options {
8+
display: flex;
9+
gap: 8px;
10+
flex-wrap: wrap;
11+
margin-bottom: 12px;
12+
}
13+
14+
.carousel-kit-setup__option {
15+
min-width: 80px;
16+
justify-content: center;
17+
}
18+
19+
.carousel-kit-setup__skip {
20+
display: block;
21+
margin-top: 4px;
22+
}
23+
}
24+
25+
// ── Viewport empty state ─────────────────────────────────────────────────────
26+
// Rendered via renderAppender inside .embla__container (a flex row).
27+
// flex: 0 0 100% makes it fill the full container width as a single flex item.
28+
.carousel-kit-viewport-empty {
29+
flex: 0 0 100%;
30+
display: flex;
31+
justify-content: center;
32+
align-items: center;
33+
padding: 2rem;
34+
border: 1px dashed #ccc;
35+
border-radius: 2px;
36+
box-sizing: border-box;
37+
}
38+
39+
// Give slides a minimum visual height in the editor so they are
40+
// easy to click and populate. Does not affect the frontend.
41+
.carousel-kit .embla__slide {
42+
min-height: 200px;
43+
}
44+
445
.carousel-kit {
546
/* Ensure selectable area */
647
padding: 0.625rem;

0 commit comments

Comments
 (0)