Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inner Block Slider: Optionally manage state externally #208

Merged
merged 7 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;