├── .travis.yml ├── example ├── bundle.css ├── index.tsx ├── index.html ├── main.css ├── dropdown.tsx └── main.tsx ├── test ├── slidedown-tests.css └── slidedown-tests.tsx ├── .gitignore ├── lib ├── slidedown.css └── slidedown.tsx ├── tsconfig.json ├── karma.conf.js ├── .vscode └── launch.json ├── LICENSE ├── package.json └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | node_js: 5 | - "8.12" 6 | addons: 7 | chrome: stable -------------------------------------------------------------------------------- /example/bundle.css: -------------------------------------------------------------------------------- 1 | @import "../lib/slidedown.css"; 2 | @import "../node_modules/purecss/build/pure.css"; 3 | @import "main.css"; -------------------------------------------------------------------------------- /test/slidedown-tests.css: -------------------------------------------------------------------------------- 1 | .react-slidedown.test-slidedown { 2 | transition-duration: .1s; 3 | transition-timing-function: linear; 4 | } 5 | 6 | .test-content { 7 | height: 18px; 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules/ 3 | coverage/ 4 | example/*.js 5 | example/*.d.ts 6 | example/*.js.map 7 | lib/**/*.js 8 | lib/**/*.d.ts 9 | lib/**/*.js.map 10 | test/*.js 11 | test/*.d.ts 12 | test/*.js.map 13 | tmp/ -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Main from './main' 4 | ReactDOM.render( 5 |
, 6 | document.getElementById('main') 7 | ) -------------------------------------------------------------------------------- /lib/slidedown.css: -------------------------------------------------------------------------------- 1 | .react-slidedown { 2 | height: 0; 3 | transition-property: none; 4 | transition-duration: .5s; 5 | transition-timing-function: ease-in-out; 6 | } 7 | 8 | .react-slidedown.transitioning { 9 | overflow-y: hidden; 10 | } 11 | 12 | .react-slidedown.closed { 13 | display: none; 14 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": [ 6 | "dom", 7 | "es5", 8 | "es2015.promise" 9 | ], 10 | "esModuleInterop": true, 11 | "importHelpers": true, 12 | "removeComments": true, 13 | "moduleResolution": "node", 14 | "sourceMap": true, 15 | "declaration": true, 16 | "jsx": "react", 17 | "noImplicitReturns": true, 18 | "noImplicitThis": false, 19 | "noFallthroughCasesInSwitch": true 20 | } 21 | } -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | port: 9876, 4 | colors: true, 5 | logLevel: config.LOG_INFO, 6 | browsers: ['ChromeHeadless'], 7 | files: [ 8 | 'lib/slidedown.css', 9 | 'test/slidedown-tests.css', 10 | 'test/slidedown-tests.js' 11 | ], 12 | frameworks: ['browserify', 'mocha'], 13 | preprocessors: { 14 | 'test/**/*.js': ['browserify'] 15 | }, 16 | reporters: ['mocha'], 17 | browserify: {} 18 | }) 19 | }; -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | react-slidedown example 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceRoot}/test/bundle.js", 12 | "outFiles": [] 13 | }, 14 | { 15 | "type": "node", 16 | "request": "attach", 17 | "name": "Attach to Process", 18 | "address": "localhost", 19 | "port": 5858, 20 | "outFiles": [] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /example/main.css: -------------------------------------------------------------------------------- 1 | .main-columns { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .main-column { 7 | display: flex; 8 | flex-direction: column; 9 | align-items: stretch; 10 | padding-left: 3em; 11 | flex: 1 1 1em; 12 | } 13 | 14 | .main-toggle { 15 | margin: .5em 3em; 16 | } 17 | 18 | .pure-menu-list { 19 | margin: .2em; 20 | } 21 | 22 | .narrative { 23 | margin: .3em 0; 24 | } 25 | 26 | .dropdown-slidedown { 27 | border: 1px solid gray; 28 | border-radius: 4px; 29 | width: 10em; 30 | max-height: 40em; 31 | background: whitesmoke; 32 | overflow-y: auto; 33 | transition-duration: .8s; 34 | transition-timing-function: ease-in-out; 35 | } 36 | 37 | .dropdown-slidedown.overlay { 38 | position: absolute; 39 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Frank Wallis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SlideDown } from '../lib/slidedown' 3 | 4 | interface DropdownProps { 5 | open: boolean; 6 | overlay: boolean; 7 | alwaysRender: boolean; 8 | itemCount: number; 9 | } 10 | 11 | export default class Dropdown extends React.Component { 12 | render() { 13 | let className = 'dropdown-slidedown' 14 | let caption = this.props.open ? 'Down' : 'Up' 15 | let render = this.props.open; 16 | let closed = false; 17 | 18 | if (this.props.overlay) { 19 | className = 'dropdown-slidedown overlay' 20 | caption = this.props.open ? 'Open' : 'Closed' 21 | } 22 | 23 | if (this.props.alwaysRender) { 24 | render = true; 25 | closed = !this.props.open; 26 | } 27 | 28 | return ( 29 |
30 | {caption} 31 | 32 | {render && this.renderList(this.props.itemCount)} 33 | 34 |
35 | ) 36 | } 37 | 38 | renderList(itemCount: number) { 39 | const items = [] 40 | for (var idx = 0; idx < itemCount; idx++) 41 | items.push(
  • {'Item ' + idx}
  • ) 42 | return
      {items}
    43 | } 44 | } -------------------------------------------------------------------------------- /example/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Dropdown from './dropdown' 3 | 4 | interface MainState { 5 | open: boolean; 6 | itemCounts: number[]; 7 | } 8 | 9 | export default class Main extends React.Component<{}, MainState> { 10 | constructor(props) { 11 | super(props) 12 | this.state = { 13 | open: false, 14 | itemCounts: this.generateItemCounts() 15 | } 16 | } 17 | 18 | generateItemCounts() { 19 | return [ 20 | Math.floor(Math.random() * 10) + 5, 21 | Math.floor(Math.random() * 30) + 5, 22 | Math.floor(Math.random() * 60) + 5, 23 | Math.floor(Math.random() * 100) + 5 24 | ] 25 | } 26 | 27 | render() { 28 | return ( 29 |
    30 | 33 | 36 |
    37 | {this.renderColumn(this.state.itemCounts[0], false, false)} 38 | {this.renderColumn(this.state.itemCounts[1], true, true)} 39 | {this.renderColumn(this.state.itemCounts[2], false, true)} 40 | {this.renderColumn(this.state.itemCounts[3], true, false)} 41 |
    42 |
    43 | ) 44 | } 45 | 46 | renderColumn(itemCount, overlay, alwaysRender) { 47 | return ( 48 |
    49 | {'I will ' + (overlay ? 'overlay' : 'push down')} 50 | 51 | {'I am ' + (overlay ? 'underneath' : 'below')} 52 |
    53 | ) 54 | } 55 | 56 | handleToggle = () => { 57 | this.setState(state => ({ open: !state.open })) 58 | } 59 | 60 | handleUpdate = () => { 61 | this.setState(_state => ({ itemCounts: this.generateItemCounts() })) 62 | } 63 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-slidedown", 3 | "version": "2.4.7", 4 | "description": "Component for animating height during mount/unmount using a CSS transition", 5 | "main": "lib/slidedown.js", 6 | "style": "lib/slidedown.css", 7 | "types": "lib/slidedown.d.ts", 8 | "scripts": { 9 | "clean": "rimraf '{example,lib,test}/**/*.{js,js.map,d.ts}'", 10 | "test": "npm run build && karma start --single-run", 11 | "test:watch": "karma start", 12 | "test:start": "concurrently \"npm run build:watch\" \"npm run test:watch\" --kill-others", 13 | "build": "tsc", 14 | "build:watch": "tsc -w", 15 | "example:watch": "watchify ./example/index.js -o ./example/bundle.js", 16 | "example:serve": "live-server --open=./example --port=5555", 17 | "start": "concurrently \"npm run build:watch\" \"npm run example:watch\" \"npm run example:serve\" --kill-others" 18 | }, 19 | "author": "Frank Wallis", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "@types/chai": "^4.1.3", 23 | "@types/enzyme": "^3.1.10", 24 | "@types/mocha": "^5.2.5", 25 | "@types/node": "^10.9.4", 26 | "@types/react": "^16.0.16", 27 | "@types/react-dom": "^16.0.5", 28 | "browserify": "^16.2.2", 29 | "chai": "^4.1.2", 30 | "concurrently": "^4.0.1", 31 | "enzyme": "^3.3.0", 32 | "enzyme-adapter-react-16": "^1.3.1", 33 | "karma": "^3.0.0", 34 | "karma-browserify": "^5.2.0", 35 | "karma-chrome-launcher": "^2.2.0", 36 | "karma-mocha": "^1.3.0", 37 | "karma-mocha-reporter": "^2.2.5", 38 | "mocha": "^5.2.0", 39 | "purecss": "^1.0.0", 40 | "react": "^16.4.0", 41 | "react-dom": "^16.4.0", 42 | "react-test-renderer": "^16.8.6", 43 | "rimraf": "^2.6.2", 44 | "typescript": "^4.5.4", 45 | "watchify": "^3.11.0" 46 | }, 47 | "dependencies": { 48 | "tslib": "^2.0.0" 49 | }, 50 | "peerDependencies": { 51 | "react": "^16.3.0 || 17", 52 | "react-dom": "^16.3.0 || 17" 53 | }, 54 | "files": [ 55 | "lib/**/*.js", 56 | "lib/**/*.js.map", 57 | "lib/**/*.css", 58 | "lib/**/*.d.ts", 59 | "lib/**/*.tsx" 60 | ], 61 | "repository": "frankwallis/react-slidedown", 62 | "keywords": [ 63 | "react", 64 | "animation", 65 | "collapse", 66 | "dropdown", 67 | "transition", 68 | "mount", 69 | "height", 70 | "auto", 71 | "dynamic", 72 | "css", 73 | "slide" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-slidedown 2 | ============================ 3 | React component which uses CSS to animate a child from its current height to ```height: auto``` when mounting/updating/unmounting. 4 | 5 | [![build status](https://secure.travis-ci.org/frankwallis/react-slidedown.png?branch=master)](http://travis-ci.org/frankwallis/react-slidedown) 6 | 7 | [Live Demo](https://ykxm1vz5vv.codesandbox.io/) 8 | 9 | ## Overview ## 10 | 11 | CSS does not currently support animating element height to ```height: auto``` and so *normally* javascript is used to achieve this effect. 12 | 13 | This component uses CSS to perform the animation, following an algorithm ([first described here](http://n12v.com/css-transition-to-from-auto)). The desired height of the element is calculated, and then css is used to transition that height. After the transition has completed the height is set to ```height: auto```. 14 | 15 | react-slidedown is perfect for dropdown lists, popup menus, accordions and closeable panels which have varying sized content. 16 | 17 | I am not aware of any cross-browser issues from IE10 and onwards. 18 | 19 | ## Installation ## 20 | 21 | ```sh 22 | npm install react-slidedown --save 23 | ``` 24 | 25 | ## Usage ## 26 | 27 | Simply wrap the component you want to slide with the ```SlideDown``` component: 28 | 29 | ```js 30 | import React from 'react' 31 | 32 | import {SlideDown} from 'react-slidedown' 33 | import 'react-slidedown/lib/slidedown.css' 34 | 35 | export function MyDropdown(props) { 36 | return ( 37 | 38 | {props.open ? props.children : null} 39 | 40 | ) 41 | } 42 | ``` 43 | 44 | In the example above the css file needed by react-slidedown is included via JavaScript which is the normal way of doing things when using [webpack css-loader](https://github.com/webpack-contrib/css-loader), it is also populated in the ```style``` property of package.json so if you are using [parcelify](https://github.com/rotundasoftware/parcelify) it should get included automatically. Otherwise it is also possibe to import it from css: 45 | 46 | ``` 47 | @import "node_modules/react-slidedown/lib/slidedown.css"; 48 | ``` 49 | 50 | ## Props 51 | 52 | | Property | Type | Default | Required? | Description | 53 | |:---|:---|:---|:---:|:---| 54 | | children | Children | | No | When provided the component opens and when removed the component closes | 55 | | closed | Boolean | | No | If `true` the component will close even if children are provided | 56 | | className | String | | No | CSS class name to be used in addition to the `react-slidedown` class name | 57 | | transitionOnAppear | Boolean | `true` | No | Do a transition animation on initial mount | 58 | | as | String | `div` | No | The outermost Element type to render | 59 | 60 | ## Example ## 61 | 62 | To quickly see a live demonstration of react-slidedown go [here](https://ykxm1vz5vv.codesandbox.io/). 63 | 64 | To build and run this example project: 65 | ``` 66 | git clone https://github.com/frankwallis/react-slidedown.git 67 | cd react-slidedown 68 | npm install 69 | npm start 70 | ``` 71 | 72 | ## Customisation ## 73 | 74 | You can customise the transition used for the animation by overriding styles on the ```SlideDown``` component: 75 | 76 | ```css 77 | .react-slidedown.my-dropdown-slidedown { 78 | transition-duration: 1.2s; 79 | transition-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1); 80 | } 81 | ``` 82 | 83 | The default values used are: 84 | 85 | ```css 86 | .react-slidedown { 87 | transition-duration: .5s; 88 | transition-timing-function: ease-in-out; 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /lib/slidedown.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef, Component } from 'react' 2 | 3 | interface SlideDownContentProps extends SlideDownProps { 4 | forwardedRef: React.Ref | null 5 | } 6 | 7 | interface SlideDownContentState { 8 | children: React.ReactNode 9 | childrenLeaving: boolean 10 | } 11 | 12 | class SlideDownContent extends Component { 13 | 14 | static defaultProps = { 15 | transitionOnAppear: true, 16 | closed: false 17 | } 18 | 19 | private outerRef: HTMLDivElement | null = null 20 | 21 | constructor(props: SlideDownContentProps) { 22 | super(props) 23 | 24 | this.state = { 25 | children: props.children, 26 | childrenLeaving: false 27 | } 28 | } 29 | 30 | private handleRef = (ref: HTMLDivElement | null) => { 31 | /* Handle both the internal and forwardedRef and maintain correct typings */ 32 | this.outerRef = ref 33 | 34 | if (this.props.forwardedRef) { 35 | if (typeof this.props.forwardedRef === 'function') { 36 | this.props.forwardedRef(ref) 37 | } else if (typeof this.props.forwardedRef === 'object') { 38 | const forwardedRef = this.props.forwardedRef as any 39 | forwardedRef.current = ref 40 | } else { 41 | throw new Error(`Invalid forwardedRef ${this.props.forwardedRef}`) 42 | } 43 | } 44 | } 45 | 46 | componentDidMount() { 47 | if (this.outerRef) { 48 | if (this.props.closed || !this.props.children) { 49 | this.outerRef.classList.add('closed') 50 | this.outerRef.style.height = '0px' 51 | } else if (this.props.transitionOnAppear) { 52 | this.startTransition('0px') 53 | } else { 54 | this.outerRef.style.height = 'auto' 55 | } 56 | } 57 | } 58 | 59 | getSnapshotBeforeUpdate() { 60 | /* Prepare to resize */ 61 | return this.outerRef ? this.outerRef.getBoundingClientRect().height + 'px' : null 62 | } 63 | 64 | static getDerivedStateFromProps(props: SlideDownContentProps, state: SlideDownContentState) { 65 | if (props.children) { 66 | return { 67 | children: props.children, 68 | childrenLeaving: false 69 | } 70 | } else if (state.children) { 71 | return { 72 | children: state.children, 73 | childrenLeaving: true 74 | } 75 | } else { 76 | return null 77 | } 78 | } 79 | 80 | componentDidUpdate(_prevProps, _prevState, snapshot: string | null) { 81 | if (this.outerRef) { 82 | this.startTransition(snapshot) 83 | } 84 | } 85 | 86 | private startTransition(prevHeight: string) { 87 | let endHeight = '0px' 88 | 89 | if (!this.props.closed && !this.state.childrenLeaving && this.state.children) { 90 | this.outerRef.classList.remove('closed') 91 | this.outerRef.style.height = 'auto' 92 | endHeight = getComputedStyle(this.outerRef).height 93 | } 94 | 95 | if (parseFloat(endHeight).toFixed(2) !== parseFloat(prevHeight).toFixed(2)) { 96 | this.outerRef.classList.add('transitioning') 97 | this.outerRef.style.height = prevHeight 98 | this.outerRef.offsetHeight // force repaint 99 | this.outerRef.style.transitionProperty = 'height' 100 | this.outerRef.style.height = endHeight 101 | } 102 | } 103 | 104 | private endTransition() { 105 | this.outerRef.classList.remove('transitioning') 106 | this.outerRef.style.transitionProperty = 'none' 107 | this.outerRef.style.height = this.props.closed ? '0px' : 'auto' 108 | 109 | if (this.props.closed || !this.state.children) { 110 | this.outerRef.classList.add('closed') 111 | } 112 | } 113 | 114 | private handleTransitionEnd = (evt: React.TransitionEvent) => { 115 | if ((evt.target === this.outerRef) && (evt.propertyName === 'height')) { 116 | if (this.state.childrenLeaving) { 117 | this.setState({ children: null, childrenLeaving: false }, () => this.endTransition()) 118 | } else { 119 | this.endTransition() 120 | } 121 | } 122 | } 123 | 124 | render() { 125 | const { as = 'div', children, className, closed, transitionOnAppear, forwardedRef, ...rest } = this.props 126 | const containerClassName = className ? 'react-slidedown ' + className : 'react-slidedown' 127 | 128 | return React.createElement( 129 | as, 130 | { 131 | ref: this.handleRef, 132 | className: containerClassName, 133 | onTransitionEnd: this.handleTransitionEnd, 134 | ...rest 135 | }, 136 | this.state.children 137 | ); 138 | } 139 | } 140 | 141 | interface SlideDownProps extends React.HTMLAttributes { 142 | as?: keyof JSX.IntrinsicElements | React.ComponentType; 143 | closed?: boolean 144 | transitionOnAppear?: boolean 145 | } 146 | 147 | export const SlideDown = forwardRef((props: SlideDownProps, ref: React.Ref) => ( 148 | 149 | )) 150 | 151 | export default SlideDown 152 | -------------------------------------------------------------------------------- /test/slidedown-tests.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import enzyme from 'enzyme' 4 | import Adapter from 'enzyme-adapter-react-16' 5 | import TestRenderer from 'react-test-renderer' 6 | import { expect } from 'chai' 7 | import { SlideDown } from '../lib/slidedown' 8 | 9 | /* Configure enzyme */ 10 | enzyme.configure({ adapter: new Adapter() }) 11 | 12 | /* async/await sleep */ 13 | function pause(millis) { 14 | return new Promise(resolve => setTimeout(resolve, millis)) 15 | } 16 | 17 | describe('SlideDown', () => { 18 | let attachTo = null 19 | 20 | beforeEach(() => { 21 | if (attachTo) { 22 | ReactDOM.unmountComponentAtNode(attachTo) 23 | } else { 24 | attachTo = document.createElement('div') 25 | document.body.appendChild(attachTo) 26 | } 27 | }) 28 | 29 | describe('simple rendering', () => { 30 | it('renders no children when empty', () => { 31 | const slidedown = enzyme.render() 32 | expect(slidedown.children()).to.have.length(0) 33 | }) 34 | 35 | it('renders content when present', () => { 36 | const slidedown = enzyme.render(findme) 37 | expect(slidedown.text()).to.equal('findme') 38 | }) 39 | 40 | it('renders container div with class react-slidedown', () => { 41 | const slidedown = enzyme.render(anything) 42 | expect(slidedown.hasClass('react-slidedown')).to.be.true 43 | }) 44 | 45 | it('adds className property to container', () => { 46 | const slidedown = enzyme.render(slideme) 47 | expect(slidedown.hasClass('react-slidedown')).to.be.true 48 | expect(slidedown.hasClass('my-class')).to.be.true 49 | }) 50 | 51 | it('adds other props to container div', () => { 52 | const ref = React.createRef() 53 | ReactDOM.render(slideme, attachTo) 54 | expect(ref.current.id).to.equal('my-id') 55 | }) 56 | 57 | it('forwards object ref to outer div', () => { 58 | const ref = React.createRef() 59 | ReactDOM.render(slideme, attachTo) 60 | expect(ref.current.tagName).to.equal('DIV') 61 | expect(ref.current.classList.contains('my-class')).to.be.true 62 | }) 63 | 64 | it('forwards function ref to outer div', () => { 65 | let current: HTMLDivElement | null = null 66 | const refFn = ref => current = ref 67 | ReactDOM.render(slideme, attachTo) 68 | expect(current.tagName).to.equal('DIV') 69 | expect(current.classList.contains('my-class')).to.be.true 70 | }) 71 | 72 | it('renders different element type when "as" prop is provided', () => { 73 | const ref = React.createRef() 74 | ReactDOM.render(slideme, attachTo) 75 | expect(ref.current.tagName).to.equal('SPAN') 76 | }) 77 | }) 78 | 79 | describe('children', () => { 80 | it('transitions when children are added', async () => { 81 | const slidedown = enzyme.mount(, { attachTo }) 82 | slidedown.setProps({ children:
    }) 83 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 84 | await pause(110) 85 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(18) 86 | }) 87 | 88 | it('transitions when children are removed', async () => { 89 | const slidedown = enzyme.mount(
    , { attachTo }) 90 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(18) 91 | slidedown.setProps({ children: null }) 92 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(18) 93 | await pause(110) 94 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 95 | }) 96 | 97 | it('reverses transition when item is removed half-way through', async () => { 98 | const slidedown = enzyme.mount(
    , { attachTo }) 99 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 100 | await pause(50) 101 | const midHeight = slidedown.find('.react-slidedown').getDOMNode().clientHeight 102 | expect(midHeight).to.be.lessThan(18) 103 | 104 | slidedown.setProps({ children: null }) 105 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(midHeight) 106 | await pause(50) 107 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.be.lessThan(midHeight) 108 | await pause(60) 109 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 110 | }) 111 | }) 112 | 113 | describe('transitionOnAppear', () => { 114 | it('transitions on mounting', async () => { 115 | const slidedown = enzyme.mount(
    , { attachTo }) 116 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 117 | await pause(110) 118 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(18) 119 | }) 120 | 121 | it('does not transition on mounting when transitionOnAppear is false', () => { 122 | const slidedown = enzyme.mount(
    , { attachTo }) 123 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(18) 124 | }) 125 | }) 126 | 127 | describe('closed property', () => { 128 | it('sets closed class on container when mounted with closed property set', () => { 129 | const slidedown = enzyme.mount(
    , { attachTo }) 130 | expect(slidedown.find('.react-slidedown').getDOMNode().classList.contains('closed')).to.be.true 131 | const slidedown2 = enzyme.mount(
    , { attachTo }) 132 | expect(slidedown2.find('.react-slidedown').getDOMNode().classList.contains('closed')).to.be.false 133 | }) 134 | 135 | it('renders children when closed property is set', () => { 136 | const slidedown = enzyme.mount(
    , { attachTo }) 137 | expect(slidedown.find('.findme')).to.have.length(1) 138 | }) 139 | 140 | it('does not transition on mounting when closed property is set', async () => { 141 | const slidedown = enzyme.mount(
    , { attachTo }) 142 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 143 | await pause(110) 144 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 145 | }) 146 | 147 | it('transitions when closed property is updated to false', async () => { 148 | const slidedown = enzyme.mount(
    , { attachTo }) 149 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 150 | slidedown.setProps({ closed: false }) 151 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 152 | await pause(110) 153 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(18) 154 | }) 155 | 156 | it('transitions when closed property is updated to true', async () => { 157 | const slidedown = enzyme.mount(
    , { attachTo }) 158 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(18) 159 | slidedown.setProps({ closed: true }) 160 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(18) 161 | await pause(110) 162 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 163 | }) 164 | 165 | it('reverses transition when closed property is set half-way through opening', async () => { 166 | const slidedown = enzyme.mount(
    , { attachTo }) 167 | await pause(50) 168 | const midHeight = slidedown.find('.react-slidedown').getDOMNode().clientHeight 169 | expect(midHeight).to.be.lessThan(18) 170 | 171 | slidedown.setProps({ closed: true }) 172 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(midHeight) 173 | await pause(50) 174 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.be.lessThan(midHeight) 175 | await pause(60) 176 | expect(slidedown.find('.react-slidedown').getDOMNode().clientHeight).to.equal(0) 177 | }) 178 | }) 179 | 180 | describe('react-test-renderer', () => { 181 | it('supports mounting', () => { 182 | const testRenderer = TestRenderer.create(slideme) 183 | expect(testRenderer.toJSON().type).to.equal('div') 184 | expect(testRenderer.toJSON().props.className).to.equal('react-slidedown') 185 | }) 186 | 187 | it('supports updating', () => { 188 | const testRenderer = TestRenderer.create(slideme) 189 | testRenderer.update(slideme) 190 | expect(testRenderer.toJSON().type).to.equal('div') 191 | expect(testRenderer.toJSON().props.className).to.equal('react-slidedown') 192 | }) 193 | 194 | it('supports unmounting', () => { 195 | const testRenderer = TestRenderer.create(slideme) 196 | testRenderer.unmount() 197 | expect(testRenderer.toJSON()).to.be.null 198 | }) 199 | }) 200 | }) --------------------------------------------------------------------------------