Skip to content

Commit

Permalink
Merge pull request #208 from humanmade/externally-manage-slide-state
Browse files Browse the repository at this point in the history
Inner Block Slider: Optionally manage state externally
  • Loading branch information
mattheu authored Oct 30, 2023
2 parents 950f3e7 + c4cb2ff commit 4df0074
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 94 deletions.
2 changes: 1 addition & 1 deletion dist/index.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-dom', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-server-side-render', 'wp-url'), 'version' => '27efa55df920e54bd9f2');
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-dom', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-server-side-render', 'wp-url'), 'version' => 'a95f545c27eab2ab7e77');
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

29 changes: 24 additions & 5 deletions src/components/InnerBlockSlider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,31 @@ The above example code creates a slider using the core image block as each slide

## Props

| Name | Type | Default | Description |
| --------------- | -------------- | ------- | --------------------------------------------- |
| `allowedBlock` | `string` | `''` | Block types to be allowed inside the slider |
| `slideLimit` | `integer` | `10` | Maximum number of slides. |
| `parentBlockId` | `string` | `''` | Client ID of parent block. This is required. |
| Name | Type | Default | Description |
| ---------------- | -------------- | ------- | --------------------------------------------- |
| `allowedBlock` | `string` | `''` | Block types to be allowed inside the slider |
| `slideLimit` | `integer` | `10` | Maximum number of slides. |
| `parentBlockId` | `string` | `''` | Client ID of parent block. This is required. |
| `showNavigation` | `bool` | `true` | Whether to show slide navigation/pagination. |

## Examples

### Managing the state externally.

If you need to sync the slider component state it with some other external functionality, you may need to manage state in your code. In order to to this, you need to import the InnerBlockSliderStateless rather than the standard container component exposed by the library.

```js
import InnerBlockSliderStateless from '@humanmade/block-editor-components';

const [ currentItemIndex, setCurrentItemIndex ] = useState( 0 );

<InnerBlockSlider
// ... other props
parentBlockId={ clientId }
currentItemIndex={ currentItemIndex }
setCurrentItemIndex={ setCurrentItemIndex }
/>
```

## Credit

Expand Down
102 changes: 15 additions & 87 deletions src/components/InnerBlockSlider/index.js
Original file line number Diff line number Diff line change
@@ -1,101 +1,29 @@
import PropTypes from 'prop-types';
import { useState, useRef, useEffect } from 'react';
import { useState } from 'react';

import { createBlock } from '@wordpress/blocks';
import { useSelect, useDispatch } from '@wordpress/data';

import InnerBlocksDisplaySingle from './inner-block-single-display';
import Navigation from './navigation';
import InnerBlockSliderControlled from './inner-block-slider-controlled.js';

/**
* InnerBlockSlider component.
* InnerBlockSlider container component.
*
* This container component is recommended for typical usage, as it handles state management.
* However for further info on supported props, refer to the InnerBlockSlider component, as all props are passed through.
*
* @param {object} props Component props.
* @param {string} props.parentBlockId Parent block clientId.
* @param {string} props.allowedBlock Allowed block type.
* @param {Array} props.template Initial block template.
* @param {number} props.slideLimit Maximum allowed slides.
* @param {object} props Component props.
* @returns {React.ReactNode} Component.
*/
const InnerBlockSlider = ( {
parentBlockId,
allowedBlock,
template,
slideLimit,
} ) => {
const innerBlockTemplate = template || [ [ allowedBlock ] ];

const slideBlocks = useSelect(
( select ) =>
select( 'core/block-editor' ).getBlock( parentBlockId ).innerBlocks
);

const InnerBlockSlider = ( props ) => {
const [ currentItemIndex, setCurrentItemIndex ] = useState( 0 );

// Track state in a ref, to allow us to determine if slides are added or removed.
const slideCount = useRef( slideBlocks.length );

const { insertBlock } = useDispatch( 'core/block-editor' );

/**
* Function to add a new slide to the group of slides.
*/
const addSlide = () => {
const created = createBlock( allowedBlock );
insertBlock( created, undefined, parentBlockId );
};

/**
* If a slide is added, switch to the new slide. If one is deleted, make sure we don't
* show a non-existent slide.
*/
useEffect( () => {
if ( slideBlocks.length > slideCount.current ) {
// Slide added
setCurrentItemIndex( slideBlocks.length - 1 );
} else if ( slideBlocks.length < slideCount.current ) {
// Slide deleted
if ( currentItemIndex + 1 > slideBlocks.length ) {
setCurrentItemIndex( slideBlocks.length - 1 );
}
}

// Update ref with new value..
slideCount.current = slideBlocks.length;
}, [ slideBlocks.length, currentItemIndex, slideCount ] );

return (
<div className="inner-block-slider">
<InnerBlocksDisplaySingle
allowedBlocks={ [ allowedBlock ] }
className="slides"
currentItemIndex={ currentItemIndex }
parentBlockId={ parentBlockId }
template={ innerBlockTemplate }
/>

<Navigation
addSlide={ addSlide }
addSlideEnabled={ slideBlocks.length < slideLimit }
currentPage={ currentItemIndex + 1 }
nextEnabled={ currentItemIndex + 1 < slideBlocks.length }
prevEnabled={ currentItemIndex + 1 > 1 }
setCurrentPage={ ( page ) => setCurrentItemIndex( page - 1 ) }
totalPages={ slideBlocks.length }
/>
</div>
<InnerBlockSliderControlled
{ ...props }
currentItemIndex={ currentItemIndex }
setCurrentItemIndex={ setCurrentItemIndex }
/>
);
};

InnerBlockSlider.defaultProps = {
slideLimit: 10,
template: null,
};

InnerBlockSlider.propTypes = {
parentBlockId: PropTypes.string.isRequired,
allowedBlock: PropTypes.string.isRequired,
template: PropTypes.array,
};
// Make controlled component available for use.
InnerBlockSlider.Controlled = InnerBlockSliderControlled;

export default InnerBlockSlider;
109 changes: 109 additions & 0 deletions src/components/InnerBlockSlider/inner-block-slider-controlled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import PropTypes from 'prop-types';
import { useRef, useEffect } from 'react';

import { createBlock } from '@wordpress/blocks';
import { useSelect, useDispatch } from '@wordpress/data';

import InnerBlocksDisplaySingle from './inner-block-single-display';
import Navigation from './navigation';

/**
* InnerBlockSlider component.
*
* @param {object} props Component props.
* @param {string} props.parentBlockId Parent block clientId.
* @param {string} props.allowedBlock Allowed block type.
* @param {Array} props.template Initial block template.
* @param {number} props.slideLimit Maximum allowed slides.
* @param {number} props.currentItemIndex Override current index, if managing this externally.
* @param {Function} props.setCurrentItemIndex Override set item Index
* @param {Function} props.showNavigation Override display nav
* @returns {React.ReactNode} Component.
*/
const InnerBlockSliderControlled = ( {
parentBlockId,
allowedBlock,
template,
slideLimit,
currentItemIndex,
setCurrentItemIndex,
showNavigation,
} ) => {
const innerBlockTemplate = template || [ [ allowedBlock ] ];

const slideBlocks = useSelect( ( select ) => select( 'core/block-editor' ).getBlock( parentBlockId ).innerBlocks );

// Track state in a ref, to allow us to determine if slides are added or removed.
const slideCount = useRef( slideBlocks.length );

const { insertBlock } = useDispatch( 'core/block-editor' );

/**
* Function to add a new slide to the group of slides.
*/
const addSlide = () => {
const created = createBlock( allowedBlock );
insertBlock( created, undefined, parentBlockId );
};

/**
* If a slide is added, switch to the new slide. If one is deleted, make sure we don't
* show a non-existent slide.
*/
useEffect( () => {
if ( slideBlocks.length > slideCount.current ) {
// Slide added
const newIndex = slideBlocks.length - 1;
setCurrentItemIndex( newIndex );
} else if ( slideBlocks.length < slideCount.current ) {
// Slide deleted
if ( currentItemIndex + 1 > slideBlocks.length ) {
const newIndex = slideBlocks.length - 1;
setCurrentItemIndex( newIndex );
}
}

// Update ref with new value..
slideCount.current = slideBlocks.length;
}, [ slideBlocks.length, currentItemIndex, slideCount, setCurrentItemIndex ] );

return (
<div className="inner-block-slider">
<InnerBlocksDisplaySingle
allowedBlocks={ [ allowedBlock ] }
className="slides"
currentItemIndex={ currentItemIndex }
parentBlockId={ parentBlockId }
template={ innerBlockTemplate }
/>

{ showNavigation && (
<Navigation
addSlide={ addSlide }
addSlideEnabled={ slideBlocks.length < slideLimit }
currentPage={ currentItemIndex + 1 }
nextEnabled={ currentItemIndex + 1 < slideBlocks.length }
prevEnabled={ currentItemIndex + 1 > 1 }
setCurrentPage={ ( page ) => setCurrentItemIndex( page - 1 ) }
totalPages={ slideBlocks.length }
/> ) }
</div>
);
};

InnerBlockSliderControlled.defaultProps = {
slideLimit: 10,
template: null,
showNavigation: true,
};

InnerBlockSliderControlled.propTypes = {
parentBlockId: PropTypes.string.isRequired,
allowedBlock: PropTypes.string.isRequired,
template: PropTypes.array,
showNavigation: PropTypes.bool,
currentItemIndex: PropTypes.number.isRequired,
setCurrentItemIndex: PropTypes.func.isRequired,
};

export default InnerBlockSliderControlled;

0 comments on commit 4df0074

Please sign in to comment.