Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into select-slide-on-change
Browse files Browse the repository at this point in the history
# Conflicts:
#	dist/index.asset.php
#	dist/index.js
#	src/components/InnerBlockSlider/index.js
  • Loading branch information
mattheu committed Oct 30, 2023
2 parents 1e608de + affa1b9 commit c349f15
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 129 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
/dist/index.asset.php
/node_modules/
/vendor/
/dist/

!.gitkeep
1 change: 0 additions & 1 deletion dist/index.asset.php

This file was deleted.

2 changes: 0 additions & 2 deletions dist/index.js

This file was deleted.

5 changes: 0 additions & 5 deletions dist/index.js.LICENSE.txt

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"start": "webpack watch --mode development",
"lint": "eslint .",
"test": "jest",
"version": "npm run build && git add -f dist/"
"version": "npm run build"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
Expand Down
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
131 changes: 16 additions & 115 deletions src/components/InnerBlockSlider/index.js
Original file line number Diff line number Diff line change
@@ -1,128 +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,
selectedBlockId,
hasSelectedInnerBlock,
} = useSelect(
( select ) => {
const blockEditorStore = select( 'core/block-editor' );
return {
slideBlocks: blockEditorStore.getBlock( parentBlockId ).innerBlocks,
selectedBlockId: blockEditorStore.getSelectedBlockClientId(),
hasSelectedInnerBlock: blockEditorStore.hasSelectedInnerBlock,
};
}
);

const { selectBlock } = useDispatch( 'core/block-editor' );
const [ currentItemIndex, setCurrentItemIndexState ] = 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
setCurrentItemIndexState( slideBlocks.length - 1 );
selectBlock( slideBlocks[ slideBlocks.length - 1 ].clientId );
} else if ( slideBlocks.length < slideCount.current && currentItemIndex + 1 > slideBlocks.length ) {
// Slide deleted
setCurrentItemIndexState( slideBlocks.length - 1 );
selectBlock( slideBlocks[ slideBlocks.length - 1 ].clientId );
}

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

/**
* If the selected block ID changes to either a slideBlock, or an Innerblock of a slide, focus that slide.
*/
useEffect( () => {
const found = slideBlocks.findIndex( ( slideBlock ) => {
return slideBlock.clientId === selectedBlockId || hasSelectedInnerBlock( slideBlock.clientId );
} );

if ( found >= 0 ) {
setCurrentItemIndexState( found );
}
}, [ selectedBlockId, slideBlocks, setCurrentItemIndexState, hasSelectedInnerBlock ] );
const InnerBlockSlider = ( props ) => {
const [ currentItemIndex, setCurrentItemIndex ] = useState( 0 );

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 ) => {
setCurrentItemIndexState( page - 1 );
selectBlock( slideBlocks[ page - 1 ].clientId );
} }
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;
142 changes: 142 additions & 0 deletions src/components/InnerBlockSlider/inner-block-slider-controlled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
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,
selectedBlockId,
hasSelectedInnerBlock,
} = useSelect(
( select ) => {
const blockEditorStore = select( 'core/block-editor' );
return {
slideBlocks: blockEditorStore.getBlock( parentBlockId ).innerBlocks,
selectedBlockId: blockEditorStore.getSelectedBlockClientId(),
hasSelectedInnerBlock: blockEditorStore.hasSelectedInnerBlock,
};
}
);

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

// 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 );
selectBlock( slideBlocks[ newIndex ].clientId );
} else if ( slideBlocks.length < slideCount.current ) {
// Slide deleted
if ( currentItemIndex + 1 > slideBlocks.length ) {
const newIndex = slideBlocks.length - 1;
setCurrentItemIndex( newIndex );
selectBlock( slideBlocks[ newIndex ].clientId );
}
}

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

/**
* If the selected block ID changes to either a slideBlock, or an Innerblock of a slide, focus that slide.
*/
useEffect( () => {
const found = slideBlocks.findIndex( ( slideBlock ) => {
return slideBlock.clientId === selectedBlockId || hasSelectedInnerBlock( slideBlock.clientId );
} );

if ( found >= 0 ) {
setCurrentItemIndex( found );
}
}, [ selectedBlockId, slideBlocks, setCurrentItemIndex, hasSelectedInnerBlock ] );

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 );
selectBlock( slideBlocks[ page - 1 ].clientId );
} }
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 c349f15

Please sign in to comment.