├── .eslintrc
├── .gitignore
├── .npmignore
├── .npmrc
├── .storybook
├── main.js
└── webpack.config.js
├── .travis.yml
├── CHANGELOG.md
├── CODEOWNERS
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── package-lock.json
├── package.json
└── src
├── components
├── Collapse.jsx
├── Collapse.test.jsx
├── __snapshots__
│ └── Collapse.test.jsx.snap
├── index.js
└── useCollapse.js
├── data
└── index.js
└── stories
├── App.jsx
├── CollapseWithOverflow.jsx
├── collapse.stories.js
├── index.js
├── perf.stories.js
└── style.css
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["airbnb", "prettier", "prettier/react"],
4 | "plugins": ["prettier", "jest", "react-hooks"],
5 | "env": {
6 | "jest": true
7 | },
8 | "globals": {
9 | "window": true,
10 | "document": true
11 | },
12 | "rules": {
13 | "linebreak-style": 0,
14 | "import/no-extraneous-dependencies": 0,
15 | "react/require-extension": 0,
16 | "react/jsx-filename-extension": [1, { "extensions": [".spec.js", ".stories.js", ".jsx"] }],
17 | "react/no-unused-prop-types": [2, { "skipShapeProps": true}],
18 | "import/no-unresolved": [1, { "caseSensitive": false }]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore build
2 | lib/
3 |
4 | # Other stuff
5 | node_modules
6 | *.log
7 | .DS_Store
8 | *.tgz
9 |
10 | npm-debug*
11 |
12 | /.idea
13 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Dot files
2 | .gitignore
3 | .eslintrc
4 | .babelrc
5 |
6 | package.json
7 | wallaby.js
8 |
9 | # Folders
10 | src/
11 | test/
12 | .storybook/
13 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ['../src/**/*.stories.@(js|jsx)'],
3 | addons: ['@storybook/addon-actions', '@storybook/addon-knobs'],
4 | };
5 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | module: {
5 | rules: [
6 | {
7 | test: /\.css$/,
8 | use: [
9 | 'style-loader',
10 | {
11 | loader: 'css-loader',
12 | options: {
13 | sourceMap: true,
14 | },
15 | },
16 | ],
17 | },
18 | ],
19 | },
20 | resolve: {
21 | extensions: ['.js', '.jsx'],
22 | modules: ['node_modules', path.resolve(__dirname, '../src')],
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - stable
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | # 4.1.0
4 | > August 24, 2020
5 | * :nut_and_bolt: **New** Add default transition
6 | * :tada: **Enhancement** Update devDependencies
7 |
8 | # 4.0.6
9 | > February 26, 2020
10 | * :bug: **Bugfix** Set overflow: visible; on initial render when isOpen is true.
11 |
12 | # 4.0.5
13 | > February 26, 2020
14 | * :bug: **Bugfix** Add npmrc and update package-lock to use npmjs registry
15 |
16 | # 4.0.4
17 | > January 14, 2020
18 | * :bug: **Bugfix** Remove animation / calculation on first render
19 |
20 | # 4.0.3
21 | > January 8, 2020
22 | * :tada: **Enhancement** Rewrite tests to use jest snapshots and @testing-library/react
23 |
24 | # 4.0.2
25 | > January 2, 2020
26 | * :tada: **Enhancement** Update build and use browserlist
27 |
28 | # 4.0.1
29 | > Desember 31, 2019
30 | * :tada: **Enhancement** Add willChange: height, add stories and prettify component
31 |
32 | # 4.0.0
33 | > Desember 31, 2019
34 | * :boom: **Breaking** Upgraded React peerDependencies to >= 16.8
35 | * :nut_and_bolt: **New** Added new hook useCollapse
36 | * :nut_and_bolt: **New** Updated Collapse to use new hook.
37 | * :tada: **Enhancement** Add dynamic content to storybook example
38 | * :tada: **Enhancement** Update prettier and eslint setup
39 |
40 | # 3.6.1
41 | > October 11, 2019
42 | * :tada: **Enhancement** Migrate away from unsafe `componentWillReceiveProps`
43 |
44 | # 3.6.1-beta.0
45 | > October 8, 2019
46 | * :tada: **Enhancement** Migrate away from unsafe `componentWillReceiveProps`
47 |
48 | # 3.6.0
49 | > October 1, 2018
50 | * :nut_and_bolt: **New** Add support for ARIA and data attributes
51 |
52 | # 3.5.0
53 | > September 3, 2018
54 | * :nut_and_bolt: **New** Add transition prop
55 | * :tada: **Enhancement** Use Jest for unit tests
56 | * :tada: **Enhancement** Add knobs to the stories
57 |
58 | # 3.4.0
59 | > August 31, 2018
60 | * :tada: **Enhancement** Handle the style changes manually when React can just do this as part of its rendering. Moved the style properties to the state and just uses setState to update them. Changed the test so the actual component styles are checked when they are updated.
61 | * :tada: **Enhancement** PureComponent is more efficient than Component so changed it to that
62 | * :tada: **Enhancement** Added a default className — would be nice not to have to pass one if you just want to use the default
63 | * :bug: **Bugfix** All Component constructors should pass the props through to the super
64 |
65 | # 3.3.2
66 | > July 05, 2018
67 | * :bug: **Bugfix** Added nullcheck to make sure we wont try to set style on nonexisting content
68 |
69 | # 3.3.1
70 | > July 05, 2018
71 | * :bug: **Bugfix** Check if element target equals content on transition end
72 |
73 | # 3.3.0
74 | > July 04, 2018
75 | * :tada: **Enhancement** Setting `visibility: hidden` when collapse is closed to prevent screenreaders from reading the content
76 |
77 | # 3.1.0
78 | > Nov 15, 2017
79 | * :nut_and_bolt: **New** Add prettier-eslint 💅
80 | * :nut_and_bolt: **New** Support react 15.x || 16.x 🕺🏼 . Solving [#14](https://github.com/SparebankenVest/react-css-collapse/issues/14)
81 | * :tada: **Enhancement** Upgrade storybook 🙏
82 |
83 | # 3.0.2
84 | > May 04, 2017
85 |
86 | * :bug: **Bugfix** Accessing PropTypes from 'prop-types' package instead of main React package. React.PropTypes will be deprecated in React 15.5
87 |
88 | # 3.0.1
89 | > Apr 30, 2017
90 |
91 | * :bug: **Bugfix** When `isOpen={true}`, set the `overflow: visible` after the component mounts to prevent cutting off content that may overflow outside the flow of `height: auto` (i.e. child content with `position: relative` and grandchildren with `position: absolute` may get cut off).
92 |
93 | # 3.0.0
94 | > Apr 7, 2017
95 |
96 | * :tada: **Feature** added `onRest` callback. The callback is triggered when your transition on `height` (specified in `className`) is done.
97 | * :boom: **Breaking** Remove `onTransitionEnd` callback. Please use `onRest` instead.
98 |
99 | # 2.1.0
100 | > Apr 7, 2017
101 |
102 | * :tada: **Feature** added `onTransitionEnd` callback that gets called after the expand/collapse animation has finished
103 |
104 | # 2.0.2
105 | > Apr 5, 2017
106 |
107 | * :bug: **Bugfix** Using setTimeout(fn, 0) to ensure correct pixelheight is set before transition (back) starts. This ensures we are not transitioning back from `auto`. Ref: http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful
108 | * :bug: **Bugfix** Checking if content is set before accessing content in requestAnimationFrame callback. This fixes a bug where `this.content` was null initially when navigating back to a page using react-css-collapse you had been to before.
109 |
110 | # 2.0.1
111 | > Apr 3, 2017
112 |
113 | * :tada: **Feature** Setup tests
114 | * :tada: **Feature** Do not require children property
115 |
116 | # 2.0.0
117 | > Mar 29, 2017
118 |
119 | * :tada: **Feature** Set initial height without transition on mount if collapse is open
120 | * :boom: **Breaking** Remove style property from collapse. We had to remove this property to prevent conflicts with crucial style properties used in the component and to set initial height without transition on mount when collapse is open.
121 |
122 | # 1.0.0
123 | > Mar 28, 2017
124 |
125 | * :nut_and_bolt: **New** Created `Collapse` component
126 |
127 | ## Examples
128 | * :nut_and_bolt: **New**
129 | * :tada: **Enhancement**
130 | * :bug: **Bugfix**
131 | * :boom: **Breaking**
132 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @SparebankenVest/react-css-collapse-maintainers
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Thanks for contributing, you rock!
2 |
3 | If you use our code, it is now *our* code.
4 |
5 | Please read https://reactjs.org/ and the Code of Conduct before opening an issue.
6 |
7 | - [Think You Found a Bug?](#bug)
8 | - [Proposing New or Changed API?](#api)
9 | - [Issue Not Getting Attention?](#attention)
10 | - [Making a Pull Request?](#pr)
11 | - [Development](#development)
12 | - [Hacking](#hacking)
13 |
14 |
15 | ## Think You Found a Bug?
16 |
17 | Please provide a test case of some sort. Best is a pull request with a failing test. Next is a link to CodePen/JS Bin or repository that illustrates the bug. Finally, some copy/pastable code is acceptable.
18 |
19 |
20 | ## Proposing New or Changed API?
21 |
22 | Please provide thoughtful comments and some sample code. Proposals without substance will be closed.
23 |
24 |
25 | ## Issue Not Getting Attention?
26 |
27 | If you need a bug fixed and nobody is fixing it, it is your responsibility to fix it. Issues with no activity for 30 days may be closed.
28 |
29 |
30 | ## Making a Pull Request?
31 |
32 | Pull requests need only the :+1: of two or more collaborators to be merged; when the PR author is a collaborator, that counts as one.
33 |
34 | ### Tests
35 |
36 | All commits that fix bugs or add features need a test.
37 |
38 | ``
39 |
40 | ### Changelog
41 |
42 | All commits that change or add to the API must be done in a pull request that also:
43 |
44 | - Adds an entry to `CHANGELOG.md` with clear steps for updating code for changed or removed API
45 | - Updates examples
46 | - Updates the docs
47 |
48 | ## Development
49 |
50 | - `npm start` build the components and watch for changes
51 | - `npm run storybook` starts a storybook that will watch for changes and build the examples
52 |
53 | ## Hacking
54 |
55 | The best way to hack on the component is to symlink it into your project using [`npm link`](https://docs.npmjs.com/cli/link). Then, use `npm start` to automatically watch the `modules` directory and output a new build every time something changes.
56 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present, Ryan Florence, Michael Jackson
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-css-collapse
2 | Collapse component with css transition for elements with variable and dynamic height.
3 |
4 | [](https://travis-ci.org/SparebankenVest/react-css-collapse)
5 | [](https://www.npmjs.com/package/react-css-collapse)
6 | [](https://www.npmjs.com/package/react-css-collapse)
7 |
8 | ## Example
9 | ### [Accordion using react-css-collapse](https://codesandbox.io/embed/accordion-using-react-css-collapse-w5r1e)
10 |
11 | ## Install
12 | [](https://npmjs.org/package/react-css-collapse)
13 |
14 | ## Support
15 | Global coverage > 92% - [browserl.ist](https://browserl.ist/?q=%22%3E0.2%25%22%2C%22not+dead%22%2C%22not+op_mini+all%22%2C%22ios_saf+%3E%3D+10%22)
16 |
17 | ## Usage
18 |
19 | ```jsx
20 | import Collapse from 'react-css-collapse';
21 |
22 |
23 |
40 |
41 |
42 | ```
43 |
44 | #### `className`: PropType.string
45 | Specify transition using the class selector with transition or the style property.
46 | The `react-css-collapse-transition` class selector is added by default unless you specify your own.
47 | The default transition can be overridden using the `transition` prop, or with custom styling 👇. Note: replace the selector with your selector if you have specified a different `className`.
48 |
49 | ```scss
50 | .react-css-collapse-transition {
51 | transition: height 250ms cubic-bezier(.4, 0, .2, 1);
52 | }
53 | ```
54 |
55 | #### `onRest`: PropTypes.func
56 | Callback function for when your transition on `height` (specified in `className`) is finished. It can be used to trigger any function after transition is done.
57 |
58 | ### ARIA and data attributes
59 |
60 | `Collapse` transfers `aria-` and `data-` attributes to the component's rendered DOM element. For example this can be used to set the `aria-hidden` attribute:
61 |
62 | ```js
63 |
64 |
54 |
55 | `;
56 |
57 | exports[`Collapse with class 1`] = `
58 |
59 |
64 |
65 | `;
66 |
67 | exports[`Collapse with style 1`] = `
68 |
69 |
74 |
75 | `;
76 |
77 | exports[`Collapse with transition prop 1`] = `
78 |
79 |
84 |
85 | `;
86 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import Collapse from './Collapse';
2 |
3 | export { default as useCollapse } from './useCollapse';
4 | export default Collapse;
5 |
--------------------------------------------------------------------------------
/src/components/useCollapse.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | function getHeight(content) {
4 | if (content && content.current && content.current.scrollHeight) {
5 | return `${content.current.scrollHeight}px`;
6 | }
7 | return '0px';
8 | }
9 |
10 | const useCollapse = ({ isOpen, content }) => {
11 | const [height, setHeight] = useState('0');
12 | const [overflow, setOverflow] = useState('hidden');
13 | const [visibility, setVisibility] = useState('hidden');
14 | const [isFirstRender, setIsFirstRender] = useState(true);
15 |
16 | const setIsExpandedStyle = () => {
17 | setHeight('auto');
18 | setOverflow('visible');
19 | setVisibility('visible');
20 | };
21 |
22 | const setIsCollapsedStyle = () => {
23 | setVisibility('hidden');
24 | };
25 |
26 | useEffect(() => {
27 | if (isOpen) {
28 | setVisibility('visible');
29 | if (isFirstRender) {
30 | setHeight('auto');
31 | setOverflow('visible');
32 | } else {
33 | setHeight(getHeight(content));
34 | }
35 | } else if (!isFirstRender) {
36 | setHeight(getHeight(content));
37 | // The magic: Set collapsed style after setting height to enable smooth transition based on height
38 | window.requestAnimationFrame(() => {
39 | // Setting these properties will start transition from measured height to 0
40 | setTimeout(() => {
41 | // Setting these properties will start transition from measured height to 0
42 | setHeight('0');
43 | setOverflow('hidden');
44 | });
45 | });
46 | }
47 | }, [isOpen]);
48 |
49 | useEffect(() => {
50 | setIsFirstRender(false);
51 | }, []);
52 |
53 | return {
54 | setIsExpandedStyle,
55 | setIsCollapsedStyle,
56 | style: {
57 | overflow,
58 | visibility,
59 | height,
60 | },
61 | };
62 | };
63 |
64 | export default useCollapse;
65 |
--------------------------------------------------------------------------------
/src/data/index.js:
--------------------------------------------------------------------------------
1 | export const text = [
2 | "You think water moves fast? You should see ice. It moves like it has a mind. Like it knows it killed the world once and got a taste for murder. After the avalanche, it took us a week to climb out. Now, I don't know exactly when we turned on each other, but I know that seven of us survived the slide... and only five made it out. Now we took an oath, that I'm breaking now. We said we'd say it was the snow that killed the other two, but it wasn't. Nature is lethal but it doesn't hold a candle to man.",
3 | "Your bones don't break, mine do. That's clear. Your cells react to bacteria and viruses differently than mine. You don't get sick, I do. That's also clear. But for some reason, you and I react the exact same way to water. We swallow it too fast, we choke. We get some in our lungs, we drown. However unreal it may seem, we are connected, you and I. We're on the same curve, just on opposite ends.",
4 | "Do you see any Teletubbies in here? Do you see a slender plastic tag clipped to my shirt with my name printed on it? Do you see a little Asian child with a blank expression on his face sitting outside on a mechanical helicopter that shakes when you put quarters in it? No? Well, that's what you see at a toy store. And you must think you're in a toy store, because you're here shopping for an infant named Jeb.",
5 | "You see? It's curious. Ted did figure it out - time travel. And when we get back, we gonna tell everyone. How it's possible, how it's done, what the dangers are. But then why fifty years in the future when the spacecraft encounters a black hole does the computer call it an 'unknown entry event'? Why don't they know? If they don't know, that means we never told anyone. And if we never told anyone it means we never made it back. Hence we die down here. Just as a matter of deductive logic.",
6 | ];
7 |
8 | export const elements = [
9 | {
10 | name: 'Earth',
11 | text: text[0],
12 | },
13 | {
14 | name: 'Air',
15 | text: text[1],
16 | },
17 | {
18 | name: 'Fire',
19 | text: text[2],
20 | },
21 | {
22 | name: 'Water',
23 | text: text[3],
24 | },
25 | ];
26 |
27 | const genText = () => Math.random().toString(36).substring(7);
28 |
29 | export const createElements = (nr) => {
30 | const list = [];
31 | for (let i = 0; i < nr; i += 1) {
32 | list.push({
33 | name: genText(),
34 | text,
35 | });
36 | }
37 | return list;
38 | };
39 |
--------------------------------------------------------------------------------
/src/stories/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, useState } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { action } from '@storybook/addon-actions';
4 | import Collapse from '../components/Collapse';
5 | import { text } from '../data';
6 |
7 | const Content = () => {
8 | const [content, setContent] = useState([text[0]]);
9 | return (
10 |
11 |
14 |