├── .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
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 | [](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 | })
--------------------------------------------------------------------------------