react-collapsible
Advanced tools
Comparing version 1.5.0 to 2.0.0
@@ -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" | ||
} | ||
} |
111
README.md
@@ -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; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
14
170
27556
3
516
1
+ Addedprop-types@^15.5.8
+ Addedreact@^15.5.4
+ Addedreact-dom@^15.5.4
+ Addedreact-dom@15.7.0(transitive)