Socket
Socket
Sign inDemoInstall

react-collapsible

Package Overview
Dependencies
21
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.5.0 to 2.0.0

dist/.gitkeep

419

dist/Collapsible.js

@@ -7,2 +7,4 @@ 'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');

@@ -12,58 +14,30 @@

var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var Collapsible = _react2.default.createClass({
displayName: 'Collapsible',
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
//Set validation for prop types
propTypes: {
transitionTime: _react2.default.PropTypes.number,
easing: _react2.default.PropTypes.string,
open: _react2.default.PropTypes.bool,
classParentString: _react2.default.PropTypes.string,
openedClassName: _react2.default.PropTypes.string,
triggerClassName: _react2.default.PropTypes.string,
triggerOpenedClassName: _react2.default.PropTypes.string,
contentOuterClassName: _react2.default.PropTypes.string,
contentInnerClassName: _react2.default.PropTypes.string,
accordionPosition: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.number]),
handleTriggerClick: _react2.default.PropTypes.func,
onOpen: _react2.default.PropTypes.func,
onClose: _react2.default.PropTypes.func,
trigger: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.element]),
triggerWhenOpen: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.element]),
triggerDisabled: _react2.default.PropTypes.bool,
lazyRender: _react2.default.PropTypes.bool,
overflowWhenOpen: _react2.default.PropTypes.oneOf(['hidden', 'visible', 'auto', 'scroll', 'inherit', 'initial', 'unset']),
triggerSibling: _react2.default.PropTypes.element
},
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
//If no transition time or easing is passed then default to this
getDefaultProps: function getDefaultProps() {
return {
transitionTime: 400,
easing: 'linear',
open: false,
classParentString: 'Collapsible',
triggerDisabled: false,
lazyRender: false,
overflowWhenOpen: 'hidden',
openedClassName: '',
triggerClassName: '',
triggerOpenedClassName: '',
contentOuterClassName: '',
contentInnerClassName: '',
className: '',
triggerSibling: null,
onOpen: function onOpen() {},
onClose: function onClose() {}
};
},
var Collapsible = function (_Component) {
_inherits(Collapsible, _Component);
//Defaults the dropdown to be closed
getInitialState: function getInitialState() {
function Collapsible(props) {
_classCallCheck(this, Collapsible);
if (this.props.open) {
return {
// Bind class methods
var _this = _possibleConstructorReturn(this, (Collapsible.__proto__ || Object.getPrototypeOf(Collapsible)).call(this, props));
_this.handleTriggerClick = _this.handleTriggerClick.bind(_this);
_this.handleTransitionEnd = _this.handleTransitionEnd.bind(_this);
_this.continueOpenCollapsible = _this.continueOpenCollapsible.bind(_this);
// Defaults the dropdown to be closed
if (_this.props.open) {
_this.state = {
isClosed: false,

@@ -74,193 +48,234 @@ shouldSwitchAutoOnNextCycle: false,

hasBeenOpened: true,
overflow: this.props.overflowWhenOpen
overflow: _this.props.overflowWhenOpen,
inTransition: false
};
} else {
return {
_this.state = {
isClosed: true,
shouldSwitchAutoOnNextCycle: false,
height: 0,
transition: 'height ' + this.props.transitionTime + 'ms ' + this.props.easing,
transition: 'height ' + _this.props.transitionTime + 'ms ' + _this.props.easing,
hasBeenOpened: false,
overflow: 'hidden'
overflow: 'hidden',
inTransition: false
};
}
},
return _this;
}
// Taken from https://github.com/EvandroLG/transitionEnd/
// Determines which prefixed event to listen for
whichTransitionEnd: function whichTransitionEnd(element) {
var transitions = {
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition': 'transitionend',
'OTransition': 'oTransitionEnd otransitionend',
'transition': 'transitionend'
};
_createClass(Collapsible, [{
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
var _this2 = this;
for (var t in transitions) {
if (element.style[t] !== undefined) {
return transitions[t];
if (this.state.shouldOpenOnNextCycle) {
this.continueOpenCollapsible();
}
}
},
componentDidMount: function componentDidMount() {
var _this = this;
if (prevState.height === 'auto' && this.state.shouldSwitchAutoOnNextCycle === true) {
window.setTimeout(function () {
// Set small timeout to ensure a true re-render
_this2.setState({
height: 0,
overflow: 'hidden',
isClosed: true,
shouldSwitchAutoOnNextCycle: false
});
}, 50);
}
//Set up event listener to listen to transitionend so we can switch the height from fixed pixel to auto for much responsiveness;
//TODO: Once Synthetic transitionend events have been exposed in the next release of React move this funciton to a function handed to the onTransitionEnd prop
this.refs.outer.addEventListener(this.whichTransitionEnd(this.refs.outer), function (event) {
if (_this.state.isClosed === false) {
_this.setState({
shouldSwitchAutoOnNextCycle: true
});
// If there has been a change in the open prop (controlled by accordion)
if (prevProps.open !== this.props.open) {
if (this.props.open === true) {
this.openCollapsible();
} else {
this.closeCollapsible();
}
}
});
},
componentDidUpdate: function componentDidUpdate(prevProps) {
if (this.state.shouldSwitchAutoOnNextCycle === true && this.state.isClosed === false) {
//Set the height to auto to make compoenent re-render with the height set to auto.
//This way the dropdown will be responsive and also change height if there is another dropdown within it.
this.makeResponsive();
}
if (this.state.shouldSwitchAutoOnNextCycle === true && this.state.isClosed === true) {
this.prepareToOpen();
}, {
key: 'closeCollapsible',
value: function closeCollapsible() {
this.setState({
shouldSwitchAutoOnNextCycle: true,
height: this.refs.inner.offsetHeight,
transition: 'height ' + this.props.transitionTime + 'ms ' + this.props.easing,
inTransition: true
});
}
}, {
key: 'openCollapsible',
value: function openCollapsible() {
this.setState({
inTransition: true,
shouldOpenOnNextCycle: true
});
}
}, {
key: 'continueOpenCollapsible',
value: function continueOpenCollapsible() {
this.setState({
height: this.refs.inner.offsetHeight,
transition: 'height ' + this.props.transitionTime + 'ms ' + this.props.easing,
isClosed: false,
hasBeenOpened: true,
inTransition: true,
shouldOpenOnNextCycle: false
});
}
}, {
key: 'handleTriggerClick',
value: function handleTriggerClick(event) {
event.preventDefault();
//If there has been a change in the open prop (controlled by accordion)
if (prevProps.open != this.props.open) {
if (this.props.open === true) {
this.openCollapsible();
if (this.props.triggerDisabled) {
return;
}
if (this.props.handleTriggerClick) {
this.props.handleTriggerClick(this.props.accordionPosition);
} else {
this.closeCollapsible();
if (this.state.isClosed === true) {
this.openCollapsible();
this.props.onOpening();
} else {
this.closeCollapsible();
this.props.onClosing();
}
}
}
},
}, {
key: 'renderNonClickableTriggerElement',
value: function renderNonClickableTriggerElement() {
if (this.props.triggerSibling && typeof this.props.triggerSibling === 'string') {
return _react2.default.createElement(
'span',
{ className: this.props.classParentString + '__trigger-sibling' },
this.props.triggerSibling
);
} else if (this.props.triggerSibling) {
return _react2.default.createElement(this.props.triggerSibling, null);
}
handleTriggerClick: function handleTriggerClick(event) {
event.preventDefault();
if (this.props.triggerDisabled) {
return;
return null;
}
if (this.props.handleTriggerClick) {
this.props.handleTriggerClick(this.props.accordionPosition);
} else {
if (this.state.isClosed === true) {
this.openCollapsible();
}, {
key: 'handleTransitionEnd',
value: function handleTransitionEnd() {
// Switch to height auto to make the container responsive
if (!this.state.isClosed) {
this.setState({ height: 'auto', inTransition: false });
this.props.onOpen();
} else {
this.closeCollapsible();
this.setState({ inTransition: false });
this.props.onClose();
}
}
},
}, {
key: 'render',
value: function render() {
var dropdownStyle = {
height: this.state.height,
WebkitTransition: this.state.transition,
msTransition: this.state.transition,
transition: this.state.transition,
overflow: this.state.overflow
};
closeCollapsible: function closeCollapsible() {
this.setState({
isClosed: true,
shouldSwitchAutoOnNextCycle: true,
height: this.refs.inner.offsetHeight,
overflow: 'hidden'
}, this.props.onClose);
},
var openClass = this.state.isClosed ? 'is-closed' : 'is-open';
var disabledClass = this.props.triggerDisabled ? 'is-disabled' : '';
openCollapsible: function openCollapsible() {
this.setState({
height: this.refs.inner.offsetHeight,
transition: 'height ' + this.props.transitionTime + 'ms ' + this.props.easing,
isClosed: false,
hasBeenOpened: true
}, this.props.onOpen);
},
//If user wants different text when tray is open
var trigger = this.state.isClosed === false && this.props.triggerWhenOpen !== undefined ? this.props.triggerWhenOpen : this.props.trigger;
makeResponsive: function makeResponsive() {
this.setState({
height: 'auto',
transition: 'none',
shouldSwitchAutoOnNextCycle: false,
overflow: this.props.overflowWhenOpen
});
},
// Don't render children until the first opening of the Collapsible if lazy rendering is enabled
var children = this.state.isClosed && !this.state.inTransition ? null : this.props.children;
prepareToOpen: function prepareToOpen() {
var _this2 = this;
// Construct CSS classes strings
var triggerClassString = this.props.classParentString + '__trigger ' + openClass + ' ' + disabledClass + ' ' + (this.state.isClosed ? this.props.triggerClassName : this.props.triggerOpenedClassName);
var parentClassString = this.props.classParentString + ' ' + (this.state.isClosed ? this.props.className : this.props.openedClassName);
var outerClassString = this.props.classParentString + '__contentOuter ' + this.props.contentOuterClassName;
var innerClassString = this.props.classParentString + '__contentInner ' + this.props.contentInnerClassName;
//The height has been changes back to fixed pixel, we set a small timeout to force the CSS transition back to 0 on the next tick.
window.setTimeout(function () {
_this2.setState({
height: 0,
shouldSwitchAutoOnNextCycle: false,
transition: 'height ' + _this2.props.transitionTime + 'ms ' + _this2.props.easing
});
}, 50);
},
renderNonClickableTriggerElement: function renderNonClickableTriggerElement() {
if (this.props.triggerSibling) {
return _react2.default.createElement(
'span',
{ className: this.props.classParentString + "__trigger-sibling" },
this.props.triggerSibling
'div',
{ className: parentClassString.trim() },
_react2.default.createElement(
'span',
{
className: triggerClassString.trim(),
onClick: this.handleTriggerClick },
trigger
),
this.renderNonClickableTriggerElement(),
_react2.default.createElement(
'div',
{
className: outerClassString.trim(),
ref: 'outer',
style: dropdownStyle,
onTransitionEnd: this.handleTransitionEnd
},
_react2.default.createElement(
'div',
{
className: innerClassString.trim(),
ref: 'inner'
},
children
)
)
);
}
}]);
return null;
},
return Collapsible;
}(_react.Component);
render: function render() {
Collapsible.propTypes = {
transitionTime: _propTypes2.default.number,
easing: _propTypes2.default.string,
open: _propTypes2.default.bool,
classParentString: _propTypes2.default.string,
openedClassName: _propTypes2.default.string,
triggerClassName: _propTypes2.default.string,
triggerOpenedClassName: _propTypes2.default.string,
contentOuterClassName: _propTypes2.default.string,
contentInnerClassName: _propTypes2.default.string,
accordionPosition: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
handleTriggerClick: _propTypes2.default.func,
onOpen: _propTypes2.default.func,
onClose: _propTypes2.default.func,
onOpening: _propTypes2.default.func,
onClosing: _propTypes2.default.func,
trigger: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.element]),
triggerWhenOpen: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.element]),
triggerDisabled: _propTypes2.default.bool,
lazyRender: _propTypes2.default.bool,
overflowWhenOpen: _propTypes2.default.oneOf(['hidden', 'visible', 'auto', 'scroll', 'inherit', 'initial', 'unset']),
triggerSibling: _propTypes2.default.oneOfType([_propTypes2.default.element, _propTypes2.default.func])
};
var dropdownStyle = {
height: this.state.height,
WebkitTransition: this.state.transition,
msTransition: this.state.transition,
transition: this.state.transition,
overflow: this.state.overflow
};
Collapsible.defaultProps = {
transitionTime: 400,
easing: 'linear',
open: false,
classParentString: 'Collapsible',
triggerDisabled: false,
lazyRender: false,
overflowWhenOpen: 'hidden',
openedClassName: '',
triggerClassName: '',
triggerOpenedClassName: '',
contentOuterClassName: '',
contentInnerClassName: '',
className: '',
triggerSibling: null,
onOpen: function onOpen() {},
onClose: function onClose() {},
onOpening: function onOpening() {},
onClosing: function onClosing() {}
};
var openClass = this.state.isClosed ? 'is-closed' : 'is-open';
var disabledClass = this.props.triggerDisabled ? 'is-disabled' : '';
//If user wants different text when tray is open
var trigger = this.state.isClosed === false && this.props.triggerWhenOpen !== undefined ? this.props.triggerWhenOpen : this.props.trigger;
// Don't render children until the first opening of the Collapsible if lazy rendering is enabled
var children = this.props.children;
if (this.props.lazyRender) if (!this.state.hasBeenOpened) children = null;
var triggerClassName = this.props.classParentString + "__trigger" + ' ' + openClass + ' ' + disabledClass;
if (this.state.isClosed) {
triggerClassName = triggerClassName + ' ' + this.props.triggerClassName;
} else {
triggerClassName = triggerClassName + ' ' + this.props.triggerOpenedClassName;
}
return _react2.default.createElement(
'div',
{ className: this.props.classParentString + ' ' + (this.state.isClosed ? this.props.className : this.props.openedClassName) },
_react2.default.createElement(
'span',
{ className: triggerClassName.trim(), onClick: this.handleTriggerClick },
trigger
),
this.renderNonClickableTriggerElement(),
_react2.default.createElement(
'div',
{ className: this.props.classParentString + "__contentOuter" + ' ' + this.props.contentOuterClassName, ref: 'outer', style: dropdownStyle },
_react2.default.createElement(
'div',
{ className: this.props.classParentString + "__contentInner" + ' ' + this.props.contentInnerClassName, ref: 'inner' },
children
)
)
);
}
});
exports.default = Collapsible;
The MIT License (MIT)
Copyright (c) 2016 Glenn Flanagan
Copyright (c) 2017 Glenn Flanagan

@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy

{
"name": "react-collapsible",
"version": "1.5.0",
"version": "2.0.0",
"description": "React component to wrap content in Collapsible element with trigger to open and close.",

@@ -16,30 +16,17 @@ "keywords": [

"devDependencies": {
"autoprefixer": "^7.1.2",
"babel": "^5.6.23",
"babel-cli": "^6.11.4",
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-preset-es2015": "^6.13.2",
"babel-preset-react": "^6.11.1",
"babelify": "^7.2.0",
"browser-sync": "^2.11.1",
"browserify": "^13.0.0",
"gulp": "^3.9.0",
"gulp-concat": "^2.6.0",
"gulp-notify": "^2.2.0",
"gulp-plumber": "^1.0.1",
"gulp-react": "^3.0.1",
"gulp-rename": "^1.2.2",
"gulp-sass": "^2.0.4",
"gulp-util": "^3.0.6",
"gulp-watch": "^4.3.5",
"react": "^0.14.7",
"react-addons-css-transition-group": "^0.14.7",
"react-addons-transition-group": "^0.14.7",
"react-dom": "^0.14.7",
"react-tools": "^0.13.3",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.7.0"
"css-loader": "^0.28.5",
"extract-text-webpack-plugin": "^3.0.0",
"node-sass": "^4.5.3",
"postcss-loader": "^2.0.6",
"sass-loader": "^6.0.6",
"webpack": "^3.5.5",
"webpack-dev-server": "^2.7.1"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0-0"
},
"repository": {

@@ -55,3 +42,8 @@ "type": "git",

"prepublish": "npm run build"
},
"dependencies": {
"prop-types": "^15.5.8",
"react": "^15.5.4",
"react-dom": "^15.5.4"
}
}

@@ -13,22 +13,14 @@ # React Responsive Collapsible Section Component (Collapsible)

## What's new in 1.5.0
* Added `onClose` and `onOpen` callback props.
---
## Migrating from v1.x to v2.0
Version 2 is 100% API complete to version 1. However, there is a breaking change in the `onOpen` and `onClose` callbacks. These methods now fire at the end of the collapsing animation. There is also the addition of `onOpening` and `onClosing` callbacks which fire at the beginning of the animation.
### 1.4.0 Notes
* Added the ability to add non-triggering elemnts to the trigger using `triggerSibling`.
To migrate to v2 from v1 simply change the `onOpen` prop to `onOpening` and `onClose` to `onClosing`.
### 1.3.0 Notes
* You can now disable triggers programatically using the `triggerDisabled` prop.
* More granular control over CSS classes allowing easier integration to your chosen CSS framework.
## What's new in 2.0
* Added `onClosing` and `onOpening` callback props.
* Several issue fixes (#12, #21, #24)
### 1.2.0 Notes
* `overflowWhenOpen` props added to allow setting of the CSS overflow property when Collapsible is open.
### 1.1.0 Notes
* `lazyRender` props added to allow lazy-loading of Collapsible content.
### 1.0.0 Notes
* Trigger can now be a React Element as well as a string.
---
## Installation

@@ -42,2 +34,3 @@ Installation can be achieved via NPM.

---
## Usage

@@ -74,67 +67,74 @@ Collapsible can receive any HTML elements or React component as it's children. Collapsible will wrap the contents, as well as generate a trigger element which will control showing and hiding.

---
## Properties *(Options)*
### `trigger` | *string* or *React Element* | **required**
### **trigger** | *string* or *React Element* | **required**
The text or element to appear in the trigger link.
### `triggerWhenOpen` | *string* or *React Element*
### **triggerWhenOpen** | *string* or *React Element*
Optional trigger text or element to change to when the Collapsible is open.
### `triggerDisabled` | *boolean* | default: false
### **triggerDisabled** | *boolean* | default: false
Disables the trigger handler if `true`. Note: this has no effect other than applying the `.is-disabled` CSS class if you've provided a `handleTriggerClick` prop.
### `transitionTime` | *number* | default: 400
### **transitionTime** | *number* | default: 400
The number of milliseconds for the open/close transition to take.
### `easing` | *string* | default: 'liner'
### **easing** | *string* | default: 'linear'
The CSS easing method you wish to apply to the open/close transition. This string can be any valid value of CSS `transition-timing-function`. For reference view the [MDN documentation](https://developer.mozilla.org/en/docs/Web/CSS/transition-timing-function).
### `open` | *bool* | default: false
### **open** | *bool* | default: false
Set to true if you want the Collapsible to begin in the open state. You can also use this prop to manage the state from a parent component.
### `classParentString` | *string* | default: Collapsible
Use this to overwrite the parent CSS class for the Collapsible component parts. Read more in the CSS section below.
### **accordionPosition** | *string*
Unique key used to identify the `Collapse` instance when used in an accordion.
### `className` | *string*
`.Collapsible` element (root) when closed
### **handleTriggerClick** | *function*
Define this to override the click handler for the trigger link. Takes one parameter, which is `props.accordionPosition`.
### `openedClassName` | *string*
`.Collapsible` element (root) when open
### **onOpen** | *function*
Is called when the Collapsible has opened.
### `triggerClassName` | *string*
`.Collapsible__trigger` element (root) when closed
### **onClose** | *function*
Is called when the Collapsible has closed.
### `triggerOpenedClassName` | *string*
`.Collapsible__trigger` element (root) when open
### `contentOuterClassName` | *string*
`.Collapsible__contentOuter` element
### `contentInnerClassName` | *string*
`.Collapsible__contentInner` element
### `accordionPosition` | *string*
Unique key used to identify the `Collapse` instance when used in an accordion.
### `handleTriggerClick` | *function*
Define this to override the click handler for the trigger link. Takes one parameter, which is `props.accordionPosition`.
### `onOpen` | *function*
### **onOpening** | *function*
Is called when the Collapsible is opening.
### `onClose` | *function*
Is called when the Collapsible is closing.
### **onClosing** | *function*
Is called when the Collapsible has closing.
### `lazyRender` | *bool* | default: false
### **lazyRender** | *bool* | default: false
Set this to true to postpone rendering of all of the content of the Collapsible until before it's opened for the first time
### `overflowWhenOpen` | *enum* | default: 'hidden'
### **overflowWhenOpen** | *enum* | default: 'hidden'
The CSS overflow property once the Collapsible is open. This can be any one of the valid CSS values of `'hidden'`, `'visible'`, `'auto'`, `'scroll'`, `'inherit'`, `'initial'`, or `'unset'`
### `triggerSibling` | *element* | default: null
### **triggerSibling** | *element* | default: null
Escape hatch to add arbitrary content on the trigger without triggering expand/collapse. It's up to you to style it as needed. This is inserted in component tree and DOM directly
after `.Collapsible__trigger`
---
## CSS Class String Props
### **classParentString** | *string* | default: Collapsible
Use this to overwrite the parent CSS class for the Collapsible component parts. Read more in the CSS section below.
### **className** | *string*
`.Collapsible` element (root) when closed
### **openedClassName** | *string*
`.Collapsible` element (root) when open
### **triggerClassName** | *string*
`.Collapsible__trigger` element (root) when closed
### **triggerOpenedClassName** | *string*
`.Collapsible__trigger` element (root) when open
### **contentOuterClassName** | *string*
`.Collapsible__contentOuter` element
### **contentInnerClassName** | *string*
`.Collapsible__contentInner` element
---
## CSS Styles

@@ -164,9 +164,10 @@ In theory you don't need any CSS to get this to work, but let's face it, it'd be pretty rubbish without it.

If you're using a CSS framework such as Foundation or Bootstrap, you probably want to use their classes instead of styling `.Collapsible`. See Properties above.
---
## Example
An example of the component in action is available in the example folder. To see it in action you can run `npm install` and then run `gulp`. This will compile all the JSX into JS and open the example page using BrowserSync.
An example of the component in action is available in the example folder. To see it in action you can run `wepback-dev-server` which will run the webpack build and open the example.
---
## Licence
React Responsive Collapsible Section Component is [MIT licensed](LICENSE.md)

@@ -1,70 +0,16 @@

import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Collapsible extends Component {
constructor(props) {
super(props)
var Collapsible = React.createClass({
// Bind class methods
this.handleTriggerClick = this.handleTriggerClick.bind(this);
this.handleTransitionEnd = this.handleTransitionEnd.bind(this);
this.continueOpenCollapsible = this.continueOpenCollapsible.bind(this);
//Set validation for prop types
propTypes: {
transitionTime: React.PropTypes.number,
easing: React.PropTypes.string,
open: React.PropTypes.bool,
classParentString: React.PropTypes.string,
openedClassName: React.PropTypes.string,
triggerClassName: React.PropTypes.string,
triggerOpenedClassName: React.PropTypes.string,
contentOuterClassName: React.PropTypes.string,
contentInnerClassName: React.PropTypes.string,
accordionPosition: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]),
handleTriggerClick: React.PropTypes.func,
onOpen: React.PropTypes.func,
onClose: React.PropTypes.func,
trigger: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.element
]),
triggerWhenOpen:React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.element
]),
triggerDisabled: React.PropTypes.bool,
lazyRender: React.PropTypes.bool,
overflowWhenOpen: React.PropTypes.oneOf([
'hidden',
'visible',
'auto',
'scroll',
'inherit',
'initial',
'unset',
]),
triggerSibling: React.PropTypes.element
},
//If no transition time or easing is passed then default to this
getDefaultProps: function() {
return {
transitionTime: 400,
easing: 'linear',
open: false,
classParentString: 'Collapsible',
triggerDisabled: false,
lazyRender: false,
overflowWhenOpen: 'hidden',
openedClassName: '',
triggerClassName: '',
triggerOpenedClassName: '',
contentOuterClassName: '',
contentInnerClassName: '',
className: '',
triggerSibling: null,
onOpen: () => {},
onClose: () => {},
};
},
//Defaults the dropdown to be closed
getInitialState: function(){
if(this.props.open){
return {
// Defaults the dropdown to be closed
if (this.props.open) {
this.state = {
isClosed: false,

@@ -75,145 +21,115 @@ shouldSwitchAutoOnNextCycle: false,

hasBeenOpened: true,
overflow: this.props.overflowWhenOpen
overflow: this.props.overflowWhenOpen,
inTransition: false,
}
}
else{
return {
} else {
this.state = {
isClosed: true,
shouldSwitchAutoOnNextCycle: false,
height: 0,
transition: 'height ' + this.props.transitionTime + 'ms ' + this.props.easing,
transition: `height ${this.props.transitionTime}ms ${this.props.easing}`,
hasBeenOpened: false,
overflow: 'hidden'
overflow: 'hidden',
inTransition: false,
}
}
},
}
// Taken from https://github.com/EvandroLG/transitionEnd/
// Determines which prefixed event to listen for
whichTransitionEnd: function(element){
var transitions = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd otransitionend',
'transition' : 'transitionend'
};
componentDidUpdate(prevProps, prevState) {
if(this.state.shouldOpenOnNextCycle){
this.continueOpenCollapsible();
}
for(var t in transitions){
if(element.style[t] !== undefined){
return transitions[t];
}
}
},
componentDidMount: function() {
//Set up event listener to listen to transitionend so we can switch the height from fixed pixel to auto for much responsiveness;
//TODO: Once Synthetic transitionend events have been exposed in the next release of React move this funciton to a function handed to the onTransitionEnd prop
this.refs.outer.addEventListener(this.whichTransitionEnd(this.refs.outer), (event) => {
if(this.state.isClosed === false){
if (prevState.height === 'auto' && this.state.shouldSwitchAutoOnNextCycle === true) {
window.setTimeout(() => { // Set small timeout to ensure a true re-render
this.setState({
shouldSwitchAutoOnNextCycle: true
height: 0,
overflow: 'hidden',
isClosed: true,
shouldSwitchAutoOnNextCycle: false,
});
}
});
},
componentDidUpdate: function(prevProps) {
if(this.state.shouldSwitchAutoOnNextCycle === true && this.state.isClosed === false) {
//Set the height to auto to make compoenent re-render with the height set to auto.
//This way the dropdown will be responsive and also change height if there is another dropdown within it.
this.makeResponsive();
}, 50);
}
if(this.state.shouldSwitchAutoOnNextCycle === true && this.state.isClosed === true) {
this.prepareToOpen();
}
//If there has been a change in the open prop (controlled by accordion)
if(prevProps.open != this.props.open) {
// If there has been a change in the open prop (controlled by accordion)
if (prevProps.open !== this.props.open) {
if(this.props.open === true) {
this.openCollapsible();
}
else {
} else {
this.closeCollapsible();
}
}
},
}
closeCollapsible() {
this.setState({
shouldSwitchAutoOnNextCycle: true,
height: this.refs.inner.offsetHeight,
transition: `height ${this.props.transitionTime}ms ${this.props.easing}`,
inTransition: true,
});
}
handleTriggerClick: function(event) {
openCollapsible() {
this.setState({
inTransition: true,
shouldOpenOnNextCycle: true,
});
}
continueOpenCollapsible() {
this.setState({
height: this.refs.inner.offsetHeight,
transition: `height ${this.props.transitionTime}ms ${this.props.easing}`,
isClosed: false,
hasBeenOpened: true,
inTransition: true,
shouldOpenOnNextCycle: false,
});
}
handleTriggerClick(event) {
event.preventDefault();
if(this.props.triggerDisabled) {
if (this.props.triggerDisabled) {
return
}
if(this.props.handleTriggerClick) {
if (this.props.handleTriggerClick) {
this.props.handleTriggerClick(this.props.accordionPosition);
}
else{
if(this.state.isClosed === true){
} else {
if (this.state.isClosed === true) {
this.openCollapsible();
}
else {
this.props.onOpening();
} else {
this.closeCollapsible();
this.props.onClosing();
}
}
}
},
closeCollapsible: function() {
this.setState({
isClosed: true,
shouldSwitchAutoOnNextCycle: true,
height: this.refs.inner.offsetHeight,
overflow: 'hidden',
}, this.props.onClose);
},
openCollapsible: function() {
this.setState({
height: this.refs.inner.offsetHeight,
transition: 'height ' + this.props.transitionTime + 'ms ' + this.props.easing,
isClosed: false,
hasBeenOpened: true
}, this.props.onOpen);
},
makeResponsive: function() {
this.setState({
height: 'auto',
transition: 'none',
shouldSwitchAutoOnNextCycle: false,
overflow: this.props.overflowWhenOpen
});
},
prepareToOpen: function() {
//The height has been changes back to fixed pixel, we set a small timeout to force the CSS transition back to 0 on the next tick.
window.setTimeout(() => {
this.setState({
height: 0,
shouldSwitchAutoOnNextCycle: false,
transition: 'height ' + this.props.transitionTime + 'ms ' + this.props.easing
});
}, 50);
},
renderNonClickableTriggerElement: function () {
if (this.props.triggerSibling) {
renderNonClickableTriggerElement() {
if (this.props.triggerSibling && typeof this.props.triggerSibling === 'string') {
return (
<span className={this.props.classParentString + "__trigger-sibling"}>{this.props.triggerSibling}</span>
<span className={`${this.props.classParentString}__trigger-sibling`}>{this.props.triggerSibling}</span>
)
} else if(this.props.triggerSibling) {
return <this.props.triggerSibling />
}
return null;
},
}
render: function () {
handleTransitionEnd() {
// Switch to height auto to make the container responsive
if (!this.state.isClosed) {
this.setState({ height: 'auto', inTransition: false });
this.props.onOpen();
} else {
this.setState({ inTransition: false });
this.props.onClose();
}
}
render() {
var dropdownStyle = {

@@ -224,34 +140,47 @@ height: this.state.height,

transition: this.state.transition,
overflow: this.state.overflow
overflow: this.state.overflow,
}
var openClass = this.state.isClosed ? 'is-closed' : 'is-open';
var disabledClass = this.props.triggerDisabled ? 'is-disabled' : ''
var disabledClass = this.props.triggerDisabled ? 'is-disabled' : '';
//If user wants different text when tray is open
var trigger = (this.state.isClosed === false) && (this.props.triggerWhenOpen !== undefined) ? this.props.triggerWhenOpen : this.props.trigger;
var trigger = (this.state.isClosed === false) && (this.props.triggerWhenOpen !== undefined)
? this.props.triggerWhenOpen
: this.props.trigger;
// Don't render children until the first opening of the Collapsible if lazy rendering is enabled
var children = this.props.children;
if(this.props.lazyRender)
if(!this.state.hasBeenOpened)
children = null;
var children = (this.state.isClosed && !this.state.inTransition) ? null : this.props.children;
let triggerClassName = this.props.classParentString + "__trigger" + ' ' + openClass + ' ' + disabledClass;
// Construct CSS classes strings
const triggerClassString = `${this.props.classParentString}__trigger ${openClass} ${disabledClass} ${
this.state.isClosed ? this.props.triggerClassName : this.props.triggerOpenedClassName
}`;
const parentClassString = `${this.props.classParentString} ${
this.state.isClosed ? this.props.className : this.props.openedClassName
}`;
const outerClassString = `${this.props.classParentString}__contentOuter ${this.props.contentOuterClassName}`;
const innerClassString = `${this.props.classParentString}__contentInner ${this.props.contentInnerClassName}`;
if (this.state.isClosed) {
triggerClassName = triggerClassName + ' ' + this.props.triggerClassName;
} else {
triggerClassName = triggerClassName + ' ' + this.props.triggerOpenedClassName;
}
return(
<div className={parentClassString.trim()}>
<span
className={triggerClassString.trim()}
onClick={this.handleTriggerClick}>
{trigger}
</span>
return(
<div className={this.props.classParentString + ' ' + (this.state.isClosed ? this.props.className : this.props.openedClassName)}>
<span className={triggerClassName.trim()} onClick={this.handleTriggerClick}>{trigger}</span>
{this.renderNonClickableTriggerElement()}
<div className={this.props.classParentString + "__contentOuter" + ' ' + this.props.contentOuterClassName } ref="outer" style={dropdownStyle}>
<div className={this.props.classParentString + "__contentInner" + ' ' + this.props.contentInnerClassName} ref="inner">
{children}
<div
className={outerClassString.trim()}
ref="outer"
style={dropdownStyle}
onTransitionEnd={this.handleTransitionEnd}
>
<div
className={innerClassString.trim()}
ref="inner"
>
{children}
</div>

@@ -262,4 +191,66 @@ </div>

}
});
}
export default Collapsible;
Collapsible.propTypes = {
transitionTime: PropTypes.number,
easing: PropTypes.string,
open: PropTypes.bool,
classParentString: PropTypes.string,
openedClassName: PropTypes.string,
triggerClassName: PropTypes.string,
triggerOpenedClassName: PropTypes.string,
contentOuterClassName: PropTypes.string,
contentInnerClassName: PropTypes.string,
accordionPosition: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
handleTriggerClick: PropTypes.func,
onOpen: PropTypes.func,
onClose: PropTypes.func,
onOpening: PropTypes.func,
onClosing: PropTypes.func,
trigger: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element
]),
triggerWhenOpen:PropTypes.oneOfType([
PropTypes.string,
PropTypes.element
]),
triggerDisabled: PropTypes.bool,
lazyRender: PropTypes.bool,
overflowWhenOpen: PropTypes.oneOf([
'hidden',
'visible',
'auto',
'scroll',
'inherit',
'initial',
'unset',
]),
triggerSibling: PropTypes.oneOfType([
PropTypes.element,
PropTypes.func,
]),
}
Collapsible.defaultProps = {
transitionTime: 400,
easing: 'linear',
open: false,
classParentString: 'Collapsible',
triggerDisabled: false,
lazyRender: false,
overflowWhenOpen: 'hidden',
openedClassName: '',
triggerClassName: '',
triggerOpenedClassName: '',
contentOuterClassName: '',
contentInnerClassName: '',
className: '',
triggerSibling: null,
onOpen: () => {},
onClose: () => {},
onOpening: () => {},
onClosing: () => {},
};
export default Collapsible;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc