React/redux wrappers for auto-scrolling react-collapse components
react-scroll-collapse
Component wrappers for auto-scrolling collapsible elements - with smooth animations powered by react-motion and react-collapse.
react-scroll-collapse provides a scrolling container (<Scroller>
) and two higher order components (collapserController, collapserControllerItem) which provide props and callbacks to use when designing your react-collapse based components.
Scroller will auto-scroll your nested components to the top when they are wrapped with either the collapserController or collapserControllerItem HoCs.
collapserController provides controls to expand/collapse all child components wrapped with collapserControllerItem.
collapserControllerItem provides controls to expand/collapse a single element.
Installation
NPM
npm install --save react-scroll-collapse
Installing peer dependencies
If you use npm@3 you'll need to install the peer dependencies
npm install --save classnames react react-collapse react-dom react-height react-motion react-redux redux redux-saga reselect
redux Integration
react-scroll-collapse relies on Redux for state management.
Importing the reducers:
import {reactScrollCollapse} from 'react-scroll-collapse';
You will need to include the 'reactScrollCollapse' reducer in your top level reducer with the same state key (i.e. reactScrollCollapse). See example
redux-saga Integration
react-scroll-collapse also relies on redux-saga
Importing the sagas:
import {reactScrollCollapseSagas} from 'react-scroll-collapse';
You will need to include 'reactScrollCollapseSagas' in your root saga - See example - which in turn must be included in your redux middleware See example
Handy tip:
redux-sagas uses generators which aren't supported widely by browsers at the moment. If you are using webpack - install the transform runtime via npm:
npm install --save-dev babel-plugin-transform-runtime
...and add it to your .babelrc file in the root of your project. It should look something like:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-transform-runtime",
"your-other-plugins"
]
}
Consult both the redux and redux-saga docs for more information about installing and using those libraries.
Component Wrappers
<Scroller>
Usage
For the auto-scroll functionality to work, components wrapped with collapserController should be nested within the scope of <Scroller>
. collapserControllerItem wrapped components should be nested in the scope of a component wrapped with collapserController.
import Scroller from 'react-scroll-collapse';
//...
<Scroller>
...
<YourComponent - wrapped by collapserController> // nested under Scroller
...
<AnotherComponent - wrapped by collapserControllerItem /> // nested under a collapserController
</YourComponent>
or...
<YourComponent - wrapped by collapserController> // nested under Scroller
...
<AnotherComponent - wrapped by collapserControllerItem /> // nested under a collapserController
<ThirdComponent wrapped by collapserController> // you can nest collapserControllers under each other
<FourthComponent wrapped by collapserControllerItem />
</ThirdComponent>
</YourComponent>
</Scroller>
You can see this component implemented in the examples folder here.
<Scroller> - Props
onRest : PropTypes.function
Scroller makes use of the react-motion <Motion>
component to perform the scrolling animation. The onRest prop is passed directly into the <Motion>
component. It is a callback that is fired when the animation is finished. Consult the react-motion docs for more information.
scrollTo : PropTypes.number
Scroller will scroll to the offsetTop value of the nested collapserControllerItem that has its expandCollapse function called. You can over ride this completely by setting a value for this prop.
offsetScrollTo : PropTypes.number
Alternatively you can set an offset that will be added to the offsetTop value.
springConfig : PropTypes.object (default: {stiffness: 170, damping: 20})
You can change the style of animation using the springConfig prop. Consult the react-motion docs on using animation springs.
className, style
You can use the usual react className and style props to style the component. A default style is applied using css modules:
:local(.scroller) {
overflow: auto;
position: relative;
}
It is possible to overwrite this default - but will probably break things.
The scroll to top functionality relies on the <Scroller>
DOM element being the first relatively positioned parent. Other then this limitation - wrapped child collapserController and collapserItemController elements can be as deeply nested in other components as you like.
collapserController()
collapserController provides controls to expand/collapse all child components wrapped with collapserControllerItem. Components wrapped with collapserController must be a child of a Scroller component (but do not have to be immediate children).
You can nest wrapped collapserController components within other collapserController components (again - they don't have to be immediate children). But - at the moment - you can't nest them within components wrapped with the collapserItemController wrapper.
collapserController will pass a 'collapserRef' prop. You must pass this as the 'ref' attribute on the DOM node you want the scroll to top animation to be applied.
Usage
import {collapserController} from 'react-scroll-collapse';
class YourComponent extends Component {
...
render() {
return (
<div
ref={this.props.collapserRef} // Don't forget to pass this ref!!!
>
...
<Component - wrapped by collapserItemController />
<Component - wrapped by collapserItemController />
...
</div>
);
}
}
export default collapserController(YourComponent);
You can see full implementations of the wrapper in the SimpleCollapser component example and the CommentThread component example
collapserController - Props
Because it's a HoC wrapper - collapserController passes props to your component.
areAllItemsExpanded : PropTypes.bool
A boolean telling your component if all the child collapserItems are expanded. Use this to display information to the user about whether it will expand its children or close them the next time expandCollapseAll is called.
If areAllItemsExpanded is true, then the next expandCollapseAll call will collapse all expand children - otherwise it will expand all children.
collapserRef : PropTypes.object
Is a ref object as created by React.createRef - you must pass this as the 'ref' attribute to a DOM node.
expandCollapseAll : PropTypes.func
Calling this function in your component will scroll your component to the top of the parent <Scroller>
component and expand/collapse any components wrapped with the collapserItemController HoC nested within the scope of the collapserController.
If some children are expanded and some collapsed then it will expand them all.
collapserId, parentCollapserId, parentScrollerId : PropTypes.number
These are the ids used in redux to track components.
collapserControllerItem()
collapserControllerItem provides controls to expand/collapse a single component that uses the react-collapse <Collapse>
component. The actual collapse/expand animation is handled by <Collapse>
so consult its docs for usage information.
collapserControllerItem will pass a 'collapserItemRef' prop. You must pass this as the 'ref' attribute on the DOM node you want the scroll to top animation to be applied.
Usage
import Collapse from 'react-collapse';
import {collapserControllerItem} from 'react-scroll-collapse';
class YourComponent extends Component {
...
render() {
const {isOpened, onHeightReady, expandCollapse} = this.props;
return (
<div
onClick={expandCollapse} // use expandCollapse as an event callback.
ref={collapserItemRef} // pass the supplied ref or auto scroll won't work.
>
...
<Collapse isOpened={isOpened}> // make sure you pass the isOpened prop to <Collapse> !!
...nested components
</Collapse>
...
</div>
);
}
}
export default collapserItemController(YourComponent);
All components wrapped with collapserControllerItem need to be a child of a component wrapped with collapserController. However, if you don't need to have multiple children of a particular collapserController then you don't need to create an additional component just to serve as the input for collapserController.
You can just wrap your collapserControllerItem immediately upon export like so:
import {collapserController, collapserControllerItem} from 'react-scroll-collapse';
class YourComponent extends Component {
...(your inner component stuff)
}
export default collapserControler(ItemController(YourComponent)); // wrap the item immediately with collapserController!
You can see full implementations of the wrapper in the SimpleComment component example and the Comment component example
collapserControllerItem - Props
Because it's a HoC wrapper - collapserItemController passes props to your component.
collapserItemRef : PropTypes.object
Is a ref object as created by React.createRef - you must pass this as the 'ref' attribute to a DOM node.
isOpened : PropTypes.bool
A boolean to pass into the <Collapse>
component (also uses isOpened) which controls the expanded state.
expandCollapse : PropTypes.func
Flips the value of the isOpened boolean - and scrolls your component to the top of the <Scroller>
component.
itemId, parentCollapserId, parentScrollerId : PropTypes.number
These are the ids used in redux to track components.
Development
Running the example demo
git clone https://github.com/danhaggard/react-scroll-collapse.git
cd react-scroll-collapse
npm install
npm start
Demo content will be served at http://localhost:8080
Testing
To run the tests:
npm test
Coverage is found in ./test/coverage/lcov-report
Acknowledgements
generator-react-webpack-redux
The original template for the project came from generator-react-webpack-redux