├── .gitattributes ├── .eslintignore ├── .gitignore ├── .vscode └── settings.json ├── jest.config.js ├── .editorconfig ├── example ├── main.css ├── index.html ├── main.js └── Example.js ├── .babelrc ├── tsconfig.json ├── src ├── getTransitionTimeMs.js ├── getTransitionTimeMs.test.js ├── index.js └── index.test.js ├── .flowconfig ├── .github └── workflows │ └── node.js.yml ├── index.d.ts ├── LICENSE.txt ├── .eslintrc.js ├── package.json ├── CHANGELOG.md ├── README.md └── flow-typed └── npm └── jest_v23.x.x.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /flow-typed 2 | /example/bundle.js 3 | /js 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | /node_modules 4 | npm-debug.log 5 | /js 6 | /example/bundle.js 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false, 3 | "flow.useNPMPackagedFlow": true 4 | } 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | module.exports = { 4 | modulePathIgnorePatterns: ['/js/'] 5 | }; 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /example/main.css: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 400px; 3 | margin: auto; 4 | } 5 | 6 | .contents { 7 | padding: 5px; 8 | border: 1px solid rgb(200,200,200); 9 | background: rgb(250,250,250); 10 | } 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | ["@babel/preset-react", { "runtime": "automatic" }], 5 | "@babel/preset-flow" 6 | ], 7 | "plugins": [ 8 | "@babel/plugin-transform-runtime" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "strict": true, 5 | "jsx": "react", 6 | "module": "commonjs", 7 | "target": "ES2017", 8 | "moduleResolution": "node", 9 | "moduleDetection": "force" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/getTransitionTimeMs.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export default function getTransitionTimeMs(heightTransition: string): number { 4 | const m = /(\d+(?:\.\d+)?|\.\d+)(m?s)\b/i.exec(heightTransition); 5 | if (!m) throw new Error('Could not parse time from transition value'); 6 | return Number(m[1]) * (m[2].length === 1 ? 1000 : 1); 7 | } 8 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/babel-.* 3 | .*/node_modules/fbjs/.* 4 | .*/node_modules/binary-extensions/.* 5 | .*/node_modules/builtin-modules/.* 6 | .*/node_modules/spdx-.*/.* 7 | .*/node_modules/.*/\(test\|lib\|example\|samplejson\)/.*\.json 8 | /js/ 9 | 10 | [include] 11 | 12 | [libs] 13 | 14 | [options] 15 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: 20 14 | cache: 'npm' 15 | - run: npm ci 16 | - run: npm test 17 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Smooth Collapse Test Page 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Props extends Omit, 'style'> { 4 | expanded: boolean; 5 | onChangeEnd?: (() => void) | null | undefined; 6 | collapsedHeight?: string; 7 | heightTransition?: string; 8 | allowOverflowWhenOpen?: boolean; 9 | eagerRender?: boolean; 10 | } 11 | 12 | export default class SmoothCollapse extends React.Component { 13 | } 14 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, {StrictMode} from 'react'; 4 | import {createRoot} from 'react-dom/client'; 5 | import Example from './Example'; 6 | 7 | const onReady = new Promise((resolve) => { 8 | if (document.readyState === 'complete') { 9 | resolve(); 10 | } else { 11 | document.addEventListener('DOMContentLoaded', resolve, false); 12 | window.addEventListener('load', resolve, false); 13 | } 14 | }); 15 | 16 | onReady.then(main).catch(e => { 17 | console.error(e, e.stack); // eslint-disable-line no-console 18 | }); 19 | 20 | function main() { 21 | const mainDiv = document.getElementById('main'); 22 | if (!mainDiv) throw new Error(); 23 | const root = createRoot(mainDiv); 24 | root.render( 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/getTransitionTimeMs.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import assert from 'assert'; 4 | 5 | import getTransitionTimeMs from '../src/getTransitionTimeMs'; 6 | 7 | describe('getTransitionTimeMs', function() { 8 | it('works with milliseconds', function() { 9 | assert.strictEqual(getTransitionTimeMs('250ms ease'), 250); 10 | }); 11 | 12 | it('works with fractional seconds', function() { 13 | assert.strictEqual(getTransitionTimeMs('.25s ease'), 250); 14 | }); 15 | 16 | it('works with real seconds', function() { 17 | assert.strictEqual(getTransitionTimeMs('10.2s ease-in'), 10.2*1000); 18 | }); 19 | 20 | it('works with integer seconds', function() { 21 | assert.strictEqual(getTransitionTimeMs('10s ease-in'), 10*1000); 22 | }); 23 | 24 | it('throws error', function() { 25 | assert.throws(() => getTransitionTimeMs('10ss ease-in')); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Rewardly, Inc. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable flowtype/require-valid-file-annotation */ 2 | module.exports = { 3 | 'parser': '@babel/eslint-parser', 4 | 'env': { 5 | 'browser': true, 6 | 'jest': true, 7 | 'node': true, 8 | 'es6': true 9 | }, 10 | 'extends': ['eslint:recommended', 'plugin:react/recommended'], 11 | 'parserOptions': { 12 | 'ecmaFeatures': { 13 | 'experimentalObjectRestSpread': true, 14 | 'jsx': true 15 | }, 16 | 'sourceType': 'module' 17 | }, 18 | 'plugins': [ 19 | 'react', 'flowtype' 20 | ], 21 | 'settings': { 22 | 'react': { 23 | 'version': '16.5', 24 | 'flowVersion': '0.81' 25 | } 26 | }, 27 | 'rules': { 28 | 'flowtype/define-flow-type': 1, 29 | 'flowtype/require-valid-file-annotation': ['error', 'always'], 30 | 31 | 'indent': ['error', 2], 32 | 'linebreak-style': ['error', 'unix'], 33 | 'quotes': ['error', 'single', 'avoid-escape'], 34 | 'semi': ['error', 'always'], 35 | 'no-var': ['error'], 36 | 'brace-style': ['error'], 37 | 'array-bracket-spacing': ['error', 'never'], 38 | 'block-spacing': ['error', 'always'], 39 | 'no-spaced-func': ['error'], 40 | 'no-whitespace-before-property': ['error'], 41 | 'space-before-blocks': ['error', 'always'], 42 | 'keyword-spacing': ['error'], 43 | 44 | 'no-unused-vars': ['warn', {'args': 'none', 'ignoreRestSiblings': true}] 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /example/Example.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable no-console, react/prop-types */ 3 | 4 | import React from 'react'; 5 | import SmoothCollapse from '../src'; 6 | 7 | export default class Example extends React.Component<{},*> { 8 | state: Object = { 9 | expanded: false 10 | }; 11 | 12 | _toggle() { 13 | this.setState({expanded: !this.state.expanded}); 14 | } 15 | 16 | render() { 17 | const {expanded} = this.state; 18 | 19 | return ( 20 |
21 |
22 |

23 | This is a demonstration of the react-smooth-collapse module. 24 |

25 |
26 | this._toggle()} 30 | /> 31 |
32 | 33 |
34 |
You did it!
35 |
36 | The contents of the collapsed region is persisted. 37 | SmoothCollapse works with contents of varying heights. Try 38 | typing text in the following text area, resizing the text area, 39 | and toggling hiding and showing this region. 40 |
41 |