Repository: reactjs/react-transition-group
Branch: master
Commit: 2989b5b87b4b
Files: 69
Total size: 173.0 KB
Directory structure:
gitextract_vqk7phwx/
├── .babelrc.js
├── .eslintignore
├── .eslintrc.yml
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.md
│ │ └── feature-request.md
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .prettierignore
├── .size-snapshot.json
├── .storybook/
│ ├── main.js
│ └── preview.js
├── CHANGELOG.md
├── LICENSE
├── Migration.md
├── README.md
├── package.json
├── prettier.config.js
├── rollup.config.js
├── src/
│ ├── CSSTransition.js
│ ├── ReplaceTransition.js
│ ├── SwitchTransition.js
│ ├── Transition.js
│ ├── TransitionGroup.js
│ ├── TransitionGroupContext.js
│ ├── config.js
│ ├── index.js
│ └── utils/
│ ├── ChildMapping.js
│ ├── PropTypes.js
│ ├── SimpleSet.js
│ └── reflow.js
├── stories/
│ ├── .eslintrc.yml
│ ├── CSSTransition.js
│ ├── CSSTransitionGroupFixture.js
│ ├── NestedTransition.js
│ ├── ReplaceTransition.js
│ ├── StoryFixture.js
│ ├── Transition.js
│ ├── TransitionGroup.js
│ ├── index.js
│ └── transitions/
│ ├── Bootstrap.js
│ ├── CSSFade.js
│ ├── CSSFadeForTransitionGroup.js
│ └── Scale.js
├── test/
│ ├── .eslintrc.yml
│ ├── CSSTransition-test.js
│ ├── CSSTransitionGroup-test.js
│ ├── ChildMapping-test.js
│ ├── SSR-test.js
│ ├── SwitchTransition-test.js
│ ├── Transition-test.js
│ ├── TransitionGroup-test.js
│ ├── setup.js
│ ├── setupAfterEnv.js
│ └── utils.js
└── www/
├── .babelrc.js
├── .gitignore
├── .npmrc
├── gatsby-config.js
├── gatsby-node.js
├── package.json
└── src/
├── components/
│ ├── Example.js
│ └── Layout.js
├── css/
│ ├── _variables.scss
│ ├── bootstrap.scss
│ └── prism-theme.scss
├── pages/
│ ├── index.js
│ ├── testing.js
│ └── with-react-router.js
└── templates/
└── component.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc.js
================================================
module.exports = {
presets: [['babel-preset-jason', { runtime: false }]],
plugins: [
['babel-plugin-transform-react-remove-prop-types', { mode: 'wrap' }],
],
env: {
esm: {
presets: [['babel-preset-jason', { modules: false }]],
},
},
};
================================================
FILE: .eslintignore
================================================
**/node_modules
www/.cache/
www/public/
lib
================================================
FILE: .eslintrc.yml
================================================
parser: babel-eslint
extends:
- jason/react
- plugin:jsx-a11y/recommended
- prettier
settings:
react:
version: detect
env:
node: true
browser: true
plugins:
- jsx-a11y
overrides:
- files: www/**/*
env:
es6: true
- files: stories/**/*
rules:
no-console: off
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: Bug report
about: Something isn't working as expected.
---
> What is the current behavior?
<!-- ... -->
> What is the expected behavior?
<!-- If your use case is complicated, please be as descriptive as possible. -->
<!-- ... -->
> Could you provide a [CodeSandbox](https://codesandbox.io/) demo reproducing the bug?
<!-- ... -->
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: Feature request
about: I have a suggestion on how to improve the library.
---
> What would you like improved?
<!-- ... -->
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [master, alpha]
pull_request:
branches: [master, alpha]
jobs:
test:
runs-on: ubuntu-latest
strategy:
# Otherwise how would we know if a specific React version caused the failure?
fail-fast: false
matrix:
REACT_DIST: [16, 17, 18]
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14
uses: actions/setup-node@v2
with:
node-version: 14
cache: 'npm'
- run: yarn
- run: yarn add react@${{ matrix.REACT_DIST }} react-dom@${{ matrix.REACT_DIST }}
- run: yarn add @testing-library/react@12
if: matrix.REACT_DIST == '17' || matrix.REACT_DIST == '16'
- run: yarn --cwd www
# Test whether the web page can be built successfully or not
- run: yarn --cwd www build
- run: yarn test
- name: Dry release
uses: cycjimmy/semantic-release-action@v2
with:
dry_run: true
semantic_version: 17
branches: |
[
'master',
{name: 'alpha', prerelease: true}
]
env:
# These are not available on forks but we need them on actual runs to verify everything is set up.
# Otherwise we might fail in the middle of a release
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
release:
needs: test
runs-on: ubuntu-latest
if: ${{ github.repository == 'reactjs/react-transition-group' &&
contains('refs/heads/master,refs/heads/alpha',
github.ref) && github.event_name == 'push' }}
steps:
- uses: styfle/cancel-workflow-action@0.9.0
- uses: actions/checkout@v2
- name: Use Node.js 14
uses: actions/setup-node@v2
with:
node-version: 14
cache: 'npm'
- run: yarn
- run: yarn build
- name: Release
uses: cycjimmy/semantic-release-action@v2
with:
semantic_version: 17
branches: |
[
'master',
{name: 'alpha', prerelease: true}
]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .gitignore
================================================
/lib
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Visual Studio Code configuration
.vscode
================================================
FILE: .prettierignore
================================================
**/node_modules
www/.cache/
www/public/
lib
*.md
================================================
FILE: .size-snapshot.json
================================================
{
"./lib/dist/react-transition-group.js": {
"bundled": 82684,
"minified": 22426,
"gzipped": 6876
},
"./lib/dist/react-transition-group.min.js": {
"bundled": 47269,
"minified": 14623,
"gzipped": 4616
}
}
================================================
FILE: .storybook/main.js
================================================
const { plugins, rules } = require('webpack-atoms');
module.exports = {
stories: ['../stories/index.js'],
webpackFinal: (config) => {
config.module = {
rules: [rules.js(), rules.astroturf(), rules.css({ extract: false })],
};
config.plugins.push(plugins.extractCss({ disable: true }));
return config;
},
};
================================================
FILE: .storybook/preview.js
================================================
import React from 'react';
export const decorators = [
(Story) => (
<React.StrictMode>
<Story />
</React.StrictMode>
),
];
================================================
FILE: CHANGELOG.md
================================================
## [4.4.5](https://github.com/reactjs/react-transition-group/compare/v4.4.4...v4.4.5) (2022-08-01)
### Bug Fixes
* apply entering animation synchronously when unmountOnExit or mountOnEnter is enabled ([#847](https://github.com/reactjs/react-transition-group/issues/847)) ([1043549](https://github.com/reactjs/react-transition-group/commit/10435492f5a5675b0e80ca6a435834ce4a0f270e))
## [4.4.4](https://github.com/reactjs/react-transition-group/compare/v4.4.3...v4.4.4) (2022-07-30)
### Bug Fixes
* missing build files ([#845](https://github.com/reactjs/react-transition-group/issues/845)) ([97af789](https://github.com/reactjs/react-transition-group/commit/97af7893b0a5bbf69211bc3287aee814123ddeea))
## [4.4.3](https://github.com/reactjs/react-transition-group/compare/v4.4.2...v4.4.3) (2022-07-30)
### Bug Fixes
* enter animations with mountOnEnter or unmountOnExit ([#749](https://github.com/reactjs/react-transition-group/issues/749)) ([51bdceb](https://github.com/reactjs/react-transition-group/commit/51bdceb96c8b6a79f417c32326ef1b31160edb97))
## [4.4.2](https://github.com/reactjs/react-transition-group/compare/v4.4.1...v4.4.2) (2021-05-29)
### Bug Fixes
* `nodeRef` prop type for cross-realm elements ([#732](https://github.com/reactjs/react-transition-group/issues/732)) ([8710c01](https://github.com/reactjs/react-transition-group/commit/8710c01549e09f55eeefec2aadb3af0a23a00f82))
## [4.4.1](https://github.com/reactjs/react-transition-group/compare/v4.4.0...v4.4.1) (2020-05-06)
### Bug Fixes
* transition SSR ([#619](https://github.com/reactjs/react-transition-group/issues/619)) ([2722bb6](https://github.com/reactjs/react-transition-group/commit/2722bb6b755943b8292f0f2bc2fdca55df5c28f0))
# [4.4.0](https://github.com/reactjs/react-transition-group/compare/v4.3.0...v4.4.0) (2020-05-05)
### Features
* add `nodeRef` alternative instead of internal `findDOMNode` ([#559](https://github.com/reactjs/react-transition-group/issues/559)) ([85016bf](https://github.com/reactjs/react-transition-group/commit/85016bfddd3831e6d7bb27926f9f178d25502913))
- react-transition-group internally uses `findDOMNode`, which is deprecated and produces warnings in [Strict Mode](https://reactjs.org/docs/strict-mode.html), so now you can optionally pass `nodeRef` to `Transition` and `CSSTransition`, it's a ref object that should point to the transitioning child:
```jsx
import React from "react"
import { CSSTransition } from "react-transition-group"
const MyComponent = () => {
const nodeRef = React.useRef(null)
return (
<CSSTransition nodeRef={nodeRef} in timeout={200} classNames="fade">
<div ref={nodeRef}>Fade</div>
</CSSTransition>
)
}
```
### Bug Fixes
* set the values of constants attached to `Transition` to match the exported ones ([#554](https://github.com/reactjs/react-transition-group/pull/554))
# [4.3.0](https://github.com/reactjs/react-transition-group/compare/v4.2.2...v4.3.0) (2019-09-05)
### Features
* upgrade dom-helpers ([#549](https://github.com/reactjs/react-transition-group/issues/549)) ([b017e18](https://github.com/reactjs/react-transition-group/commit/b017e18))
## [4.2.2](https://github.com/reactjs/react-transition-group/compare/v4.2.1...v4.2.2) (2019-08-02)
### Bug Fixes
* Fix imports to play nicely with rollup ([#530](https://github.com/reactjs/react-transition-group/issues/530)) ([3d9003e](https://github.com/reactjs/react-transition-group/commit/3d9003e))
## [4.2.1](https://github.com/reactjs/react-transition-group/compare/v4.2.0...v4.2.1) (2019-07-02)
### Bug Fixes
* updated SwitchTransition component to be default export and exported from index.js ([#516](https://github.com/reactjs/react-transition-group/issues/516)) ([cfd0070](https://github.com/reactjs/react-transition-group/commit/cfd0070))
# [4.2.0](https://github.com/reactjs/react-transition-group/compare/v4.1.1...v4.2.0) (2019-06-28)
### Features
* add SwitchTransition component ([#470](https://github.com/reactjs/react-transition-group/issues/470)) ([c5e379d](https://github.com/reactjs/react-transition-group/commit/c5e379d))
## [4.1.1](https://github.com/reactjs/react-transition-group/compare/v4.1.0...v4.1.1) (2019-06-10)
### Bug Fixes
* adds missing dependency [@babel](https://github.com/babel)/runtime ([#507](https://github.com/reactjs/react-transition-group/issues/507)) ([228bf5f](https://github.com/reactjs/react-transition-group/commit/228bf5f))
# [4.1.0](https://github.com/reactjs/react-transition-group/compare/v4.0.1...v4.1.0) (2019-05-30)
### Features
* add global transition disable switch ([#506](https://github.com/reactjs/react-transition-group/issues/506)) ([4c5ba98](https://github.com/reactjs/react-transition-group/commit/4c5ba98))
## [4.0.1](https://github.com/reactjs/react-transition-group/compare/v4.0.0...v4.0.1) (2019-05-09)
### Bug Fixes
* issue with dynamically applied classes not being properly removed for reentering items ([#499](https://github.com/reactjs/react-transition-group/issues/499)) ([129cb11](https://github.com/reactjs/react-transition-group/commit/129cb11))
# [4.0.0](https://github.com/reactjs/react-transition-group/compare/v3.0.0...v4.0.0) (2019-04-16)
### Features
* support esm via package.json routes ([#488](https://github.com/reactjs/react-transition-group/issues/488)) ([6337bf5](https://github.com/reactjs/react-transition-group/commit/6337bf5)), closes [#77](https://github.com/reactjs/react-transition-group/issues/77)
### BREAKING CHANGES
* in environments where esm is supported importing from commonjs requires explicitly adding the `.default` after `require()` when resolving to the esm build
# [3.0.0](https://github.com/reactjs/react-transition-group/compare/v2.9.0...v3.0.0) (2019-04-15)
### Features
* use stable context API ([#471](https://github.com/reactjs/react-transition-group/issues/471)) ([aee4901](https://github.com/reactjs/react-transition-group/commit/aee4901)), closes [#429](https://github.com/reactjs/react-transition-group/issues/429)
### BREAKING CHANGES
* use new style react context
```diff
// package.json
-"react": "^15.0.0",
+"react": "^16.6.0",
-"react-dom": "^15.0.0",
+"react-dom": "^16.6.0",
```
# [2.9.0](https://github.com/reactjs/react-transition-group/compare/v2.8.0...v2.9.0) (2019-04-06)
### Features
* **CSSTransition:** add "done" class for appear ([fe3c156](https://github.com/reactjs/react-transition-group/commit/fe3c156)), closes [#383](https://github.com/reactjs/react-transition-group/issues/383) [#327](https://github.com/reactjs/react-transition-group/issues/327) [#327](https://github.com/reactjs/react-transition-group/issues/327)
### Reverts
* bump semantic release dependencies ([1bdcaec](https://github.com/reactjs/react-transition-group/commit/1bdcaec))
# [2.8.0](https://github.com/reactjs/react-transition-group/compare/v2.7.1...v2.8.0) (2019-04-02)
### Features
* add support for empty classNames ([#481](https://github.com/reactjs/react-transition-group/issues/481)) ([d755dc6](https://github.com/reactjs/react-transition-group/commit/d755dc6))
## [2.7.1](https://github.com/reactjs/react-transition-group/compare/v2.7.0...v2.7.1) (2019-03-25)
### Bug Fixes
* revert tree-shaking support because it was a breaking change ([271364c](https://github.com/reactjs/react-transition-group/commit/271364c))
# [2.7.0](https://github.com/reactjs/react-transition-group/compare/v2.6.1...v2.7.0) (2019-03-22)
### Features
* support ESM (tree-shaking) ([#455](https://github.com/reactjs/react-transition-group/issues/455)) ([ef3e357](https://github.com/reactjs/react-transition-group/commit/ef3e357))
## [2.6.1](https://github.com/reactjs/react-transition-group/compare/v2.6.0...v2.6.1) (2019-03-14)
### Bug Fixes
* **Transition:** make `exit` key optional when passing an object to the `timeout` prop ([#464](https://github.com/reactjs/react-transition-group/pull/464)) ([3a4cf9c](https://github.com/reactjs/react-transition-group/commit/3a4cf9c91ab5f25caaa9501b129bce66ec9bb56b))
* **package.json:** mark react-transition-group as side-effect free for webpack tree shaking ([#472](https://github.com/reactjs/react-transition-group/issues/472)) ([b81dc89](https://github.com/reactjs/react-transition-group/commit/b81dc89))
# [2.6.0](https://github.com/reactjs/react-transition-group/compare/v2.5.3...v2.6.0) (2019-02-26)
### Features
* add appear timeout ([#462](https://github.com/reactjs/react-transition-group/issues/462)) ([52cdc34](https://github.com/reactjs/react-transition-group/commit/52cdc34))
## [2.5.3](https://github.com/reactjs/react-transition-group/compare/v2.5.2...v2.5.3) (2019-01-14)
### Bug Fixes
* strip custom prop-types in production ([#448](https://github.com/reactjs/react-transition-group/issues/448)) ([46fa20f](https://github.com/reactjs/react-transition-group/commit/46fa20f))
## [2.5.2](https://github.com/reactjs/react-transition-group/compare/v2.5.1...v2.5.2) (2018-12-20)
### Bug Fixes
* pass appear to CSSTransition callbacks ([#441](https://github.com/reactjs/react-transition-group/issues/441)) ([df7adb4](https://github.com/reactjs/react-transition-group/commit/df7adb4)), closes [#143](https://github.com/reactjs/react-transition-group/issues/143)
## [2.5.1](https://github.com/reactjs/react-transition-group/compare/v2.5.0...v2.5.1) (2018-12-10)
### Bug Fixes
* prevent calling setState in TransitionGroup if it has been unmounted ([#435](https://github.com/reactjs/react-transition-group/issues/435)) ([6d46b69](https://github.com/reactjs/react-transition-group/commit/6d46b69))
# [2.5.0](https://github.com/reactjs/react-transition-group/compare/v2.4.0...v2.5.0) (2018-09-26)
### Features
* update build and package dependencies ([#413](https://github.com/reactjs/react-transition-group/issues/413)) ([af3d45a](https://github.com/reactjs/react-transition-group/commit/af3d45a))
# [2.4.0](https://github.com/reactjs/react-transition-group/compare/v2.3.1...v2.4.0) (2018-06-27)
### Features
* remove deprecated lifecycle hooks and polyfill for older react versions ([c1ab1cf](https://github.com/reactjs/react-transition-group/commit/c1ab1cf))
### Performance Improvements
* don't reflow when there's no class to add ([d7b898d](https://github.com/reactjs/react-transition-group/commit/d7b898d))
<a name="2.3.1"></a>
## [2.3.1](https://github.com/reactjs/react-transition-group/compare/v2.3.0...v2.3.1) (2018-04-14)
### Bug Fixes
* **deps:** Move loose-envify and semantic-release to devDependencies ([#319](https://github.com/reactjs/react-transition-group/issues/319)) ([b4ec774](https://github.com/reactjs/react-transition-group/commit/b4ec774))
## [v2.3.0]
> 2018-03-28
* Added `*-done` classes to CSS Transition ([#269])
* Reorganize docs with more interesting examples! ([#304])
* A bunch of bug fixes
[#269]: https://github.com/reactjs/react-transition-group/pull/269
[#304]: https://github.com/reactjs/react-transition-group/pull/304
[v2.3.0]: https://github.com/reactjs/react-transition-group/compare/v2.2.1...2.3.0
## [v2.2.1]
> 2017-09-29
* **Patch:** Allow React v16 ([#198])
[#198]: https://github.com/reactjs/react-transition-group/pull/198
[v2.2.1]: https://github.com/reactjs/react-transition-group/compare/v2.2.0...2.2.1
## [v2.2.0]
> 2017-07-21
* **Feature:** Support multiple classes in `classNames` ([#124])
* **Docs:** fix broken link ([#127])
* **Bugfix:** Fix Transition props pass-through ([#123])
[#124]: https://github.com/reactjs/react-transition-group/pull/124
[#123]: https://github.com/reactjs/react-transition-group/pull/123
[#127]: https://github.com/reactjs/react-transition-group/pull/127
[v2.2.0]: https://github.com/reactjs/react-transition-group/compare/v2.1.0...2.2.0
## [v2.1.0]
> 2017-07-06
* **Feature:** Add back `childFactory` on `<TransitionGroup>` ([#113])
* **Bugfix:** Ensure child specified `onExited` fires in a `<TransitionGroup>` ([#113])
[#113]: https://github.com/reactjs/react-transition-group/pull/113
[v2.1.0]: https://github.com/reactjs/react-transition-group/compare/v2.0.1...2.1.0
## v2.0.2
> 2017-07-06
* **Fix documentation npm:** No code changes
## v2.0.1
> 2017-07-06
* **Fix documentation on npm:** No code changes
## [v2.0.0]
> 2017-07-06
* **Feature:** New API! ([#24]), migration guide at [https://github.com/reactjs/react-transition-group/blob/master/Migration.md](https://github.com/reactjs/react-transition-group/blob/master/Migration.md)
[#24]: https://github.com/reactjs/react-transition-group/pull/24
[v2.0.0]: https://github.com/reactjs/react-transition-group/compare/v1.2.0...v2.0.0
## [v1.2.0]
> 2017-06-12
* **Feature:** Dist build now includes both production and development builds ([#64])
* **Feature:** PropTypes are now wrapped allowing for lighter weight production builds ([#69])
[#64]: https://github.com/reactjs/react-transition-group/issues/64
[#69]: https://github.com/reactjs/react-transition-group/issues/69
[v1.1.x]: https://github.com/reactjs/react-transition-group/compare/v1.1.3...master
## [v1.1.3]
> 2017-05-02
* bonus release, no additions
[v1.1.3]: https://github.com/reactjs/react-transition-group/compare/v1.1.2...v1.1.3
## [v1.1.2]
> 2017-05-02
* **Bugfix:** Fix refs on children ([#39])
[v1.1.2]: https://github.com/reactjs/react-transition-group/compare/v1.1.1...v1.1.2
[#39]: https://github.com/reactjs/react-transition-group/pull/39
## [v1.1.1]
> 2017-03-16
* **Chore:** Add a prebuilt version of the library for jsbin and the like.
[v1.1.1]: https://github.com/reactjs/react-transition-group/compare/v1.1.0...v1.1.1
## [v1.1.0]
> 2017-03-16
* **Feature:** Support refs on children ([#9])
* **Feature:** TransitionChild to passes props through ([#4])
* **Bugfix:** Fix TransitionGroup error on quick toggle of components ([#15])
* **Bugfix:** Fix to work enter animation with CSSTransitionGroup ([#13])
[v1.1.0]: https://github.com/reactjs/react-transition-group/compare/v1.0.0...v1.1.0
[#15]: https://github.com/reactjs/react-transition-group/pull/15
[#13]: https://github.com/reactjs/react-transition-group/pull/13
[#9]: https://github.com/reactjs/react-transition-group/pull/9
[#4]: https://github.com/reactjs/react-transition-group/pull/4
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2018, React Community
Forked from React (https://github.com/facebook/react) Copyright 2013-present, Facebook, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: Migration.md
================================================
# Migration Guide from v1 to v2
_A few notes to help with migrating from v1 to v2._
The `<CSSTransitionGroup>` component has been removed. A `<CSSTransition>` component has been added for use with the new `<TransitionGroup>` component to accomplish the same tasks.
### tl;dr:
- `transitionName` -> `classNames`
- `transitionEnterTimeout` and `transitionLeaveTimeout` -> `timeout={{ exit, enter }}`
- `transitionAppear` -> `appear`
- `transitionEnter` -> `enter`
- `transitionLeave` -> `exit`
## Walkthrough
Let's take the [original docs example](https://github.com/reactjs/react-transition-group/tree/v1-stable/#high-level-api-csstransitiongroup) and migrate it.
Starting with this CSS:
```css
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.example-leave {
opacity: 1;
}
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
```
And this component:
```js
class TodoList extends React.Component {
constructor(props) {
super(props);
this.state = {items: ['hello', 'world', 'click', 'me']};
this.handleAdd = this.handleAdd.bind(this);
}
handleAdd() {
const newItems = this.state.items.concat([
prompt('Enter some text')
]);
this.setState({items: newItems});
}
handleRemove(i) {
let newItems = this.state.items.slice();
newItems.splice(i, 1);
this.setState({items: newItems});
}
render() {
const items = this.state.items.map((item, i) => (
<div key={i} onClick={() => this.handleRemove(i)}>
{item}
</div>
));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
<CSSTransitionGroup
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{items}
</CSSTransitionGroup>
</div>
);
}
}
```
The most straightforward way to migrate is to use `<TransitionGroup>` instead of `<CSSTransitionGroup>`:
```diff
render() {
const items = this.state.items.map((item, i) => (
<div key={i} onClick={() => this.handleRemove(i)}>
{item}
</div>
));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
- <CSSTransitionGroup
- transitionName="example"
- transitionEnterTimeout={500}
- transitionLeaveTimeout={300}>
+ <TransitionGroup>
{items}
- </CSSTransitionGroup>
+ </TransitionGroup>
</div>
)
}
```
That doesn't get us much, since we haven't included anything to do the animation. For that, we'll need to wrap each item in a `<CSSTransition>`. First, though, let's adjust our CSS:
```diff
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
-.example-leave {
+.example-exit {
opacity: 1;
}
-.example-leave.example-leave-active {
+.example-exit.example-exit-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
```
All we did was replace `leave` with `exit`. v2 uses "exit" instead of "leave" to be more symmetric, avoiding awkwardness with English tenses (like with "entered" and "leaved").
Now we add the `<CSSTransition>` component:
```diff
render() {
const items = this.state.items.map((item, i) => (
+ <CSSTransition
+ key={i}
+ classNames="example"
+ timeout={{ enter: 500, exit: 300 }}
+ >
<div onClick={() => this.handleRemove(i)}>
{item}
</div>
+ </CSSTransition>
));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
<TransitionGroup>
{items}
</TransitionGroup>
</div>
)
}
```
Note that we replaced `transitionName` with `classNames`. `<CSSTransition>` otherwise has essentially the same signature as `<CSSTransitionGroup>`. We also replaced `transitionEnterTimeout` and `transitionLeaveTimeout` with a single `timeout` prop with an object.
> **Hint:** If your enter and exit timeouts are the same you can use the shorthand `timeout={500}`.
If we want to make this a bit more encapsulated, we can wrap our `<CSSTransition>` into a separate component for reuse later:
```js
const FadeTransition = (props) => (
<CSSTransition
{...props}
classNames="example"
timeout={{ enter: 500, exit: 300 }}
/>
);
```
We can then use it like:
```diff
render() {
const items = this.state.items.map((item, i) => (
- <CSSTransition
- key={i}
- classNames="example"
- timeout={{ enter: 500, exit: 300 }}
- >
+ <FadeTransition key={i}>
<div onClick={() => this.handleRemove(i)}>
{item}
</div>
- </CSSTransition>
+ </FadeTransition>
));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
<TransitionGroup>
{items}
</TransitionGroup>
</div>
)
}
```
> **Hey!** You may not need `<CSSTransition>` at all! The lower level `<Transition>` component is very flexible and may be easier to work with for simpler or more custom cases. Check out how we migrated [React-Bootstrap](https://react-bootstrap.github.io/)'s simple transitions to v2 for the [`<Collapse>`](https://github.com/react-bootstrap/react-bootstrap/pull/2676/files#diff-4f938f648d04d4859be417d6590ca7c4) and [`<Fade>`](https://github.com/react-bootstrap/react-bootstrap/pull/2676/files#diff-8f766132cbd9f8de55ee05d63d75abd8) components.
## Wrapping `<Transition>` Components
The old `<TransitionGroup>` component managed transitions through custom static lifecycle methods on its children. In v2 we removed that API in favor of requiring that `<TransitionGroup>` be used with a `<Transition>` component, and using traditional prop passing to communicate between the two.
This means that `<TransitionGroup>`s inject their children with `<Transition>`-specific props that _must_ be passed through to the `<Transition>` component for the transition to work.
```js
const MyTransition = ({ children: child, ...props }) => (
// NOTICE THE SPREAD! THIS IS REQUIRED!
<Transition {...props}>
{transitionState => React.cloneElement(child, {
style: getStyleForTransitionState(transitionState)
})}
</Transition>
);
const MyList = () => (
<TransitionGroup>
{items.map(item => (
<MyTransition>{item}</MyTransition>
)}
</TransitionGroup>
);
```
Note how `<MyTransition>` passes all props other than its own to `<Transition>`.
## Lifecycle Callbacks
As noted, child lifecycle methods have been removed. If you do need to do some work when the `<Transition>` changes from one state to another, use the lifecycle callback props.
```js
<Transition
{...props}
onEnter={handleEnter}
onEntering={handleEntering}
onEntered={handleEntered}
onExit={handleExit}
onExiting={handleExiting}
onExited={handleExited}
/>
```
Each callback is called with the DOM node of the transition component. Note also that there are now _three_ states per enter/exit transition instead of the original two. See the [full documentation](https://reactcommunity.org/react-transition-group/#Transition) for more details.
================================================
FILE: README.md
================================================
# react-transition-group [![npm][npm-badge]][npm]
> **ATTENTION!** To address many issues that have come up over the years, the API in v2 and above is not backwards compatible with the original [`React addon (v1-stable)`](https://github.com/reactjs/react-transition-group/tree/v1-stable).
>
> **For a drop-in replacement for `react-addons-transition-group` and `react-addons-css-transition-group`, use the v1 release. Documentation and code for that release are available on the [`v1-stable`](https://github.com/reactjs/react-transition-group/tree/v1-stable) branch.**
>
> We are no longer updating the v1 codebase, please upgrade to the latest version when possible
A set of components for managing component states (including mounting and unmounting) over time, specifically designed with animation in mind.
## Documentation
- [**Main documentation**](https://reactcommunity.org/react-transition-group/)
- [Migration guide from v1](/Migration.md)
## TypeScript
TypeScript definitions are published via [**DefinitelyTyped**](https://github.com/DefinitelyTyped/DefinitelyTyped) and can be installed via the following command:
```
npm install @types/react-transition-group
```
## Examples
Clone the repo first:
```
git@github.com:reactjs/react-transition-group.git
```
Then run `npm install` (or `yarn`), and finally `npm run storybook` to start a storybook instance that you can navigate to in your browser to see the examples.
[npm-badge]: https://img.shields.io/npm/v/react-transition-group.svg
[npm]: https://www.npmjs.org/package/react-transition-group
================================================
FILE: package.json
================================================
{
"name": "react-transition-group",
"version": "4.4.5",
"description": "A react component toolset for managing animations",
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"scripts": {
"test": "npm run lint && npm run testonly",
"testonly": "jest --verbose",
"tdd": "jest --watch",
"build": "rimraf lib && yarn build:cjs && yarn build:esm && yarn build:pick && yarn build:dist && cp README.md LICENSE ./lib",
"build:docs": "yarn --cwd www run build",
"build:cjs": "babel src --out-dir lib/cjs",
"build:esm": "cross-env BABEL_ENV=esm babel src --out-dir lib/esm",
"build:pick": "cherry-pick --cwd=lib --input-dir=../src --cjs-dir=cjs --esm-dir=esm",
"build:dist": "cross-env BABEL_ENV=esm rollup -c",
"bootstrap": "yarn && yarn --cwd www",
"fix": "run-s fix:eslint fix:prettier",
"fix:eslint": "yarn lint:eslint --fix",
"fix:prettier": "yarn lint:prettier --write",
"lint": "run-p lint:*",
"lint:eslint": "eslint .",
"lint:prettier": "prettier . --check",
"release": "release",
"release:next": "release --preid beta --tag next",
"deploy-docs": "yarn --cwd www run deploy",
"start": "yarn --cwd www run develop",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook",
"semantic-release": "semantic-release"
},
"repository": {
"type": "git",
"url": "https://github.com/reactjs/react-transition-group.git"
},
"keywords": [
"react",
"transition",
"addons",
"transition-group",
"animation",
"css",
"transitions"
],
"author": "",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/reactjs/react-transition-group/issues"
},
"homepage": "https://github.com/reactjs/react-transition-group#readme",
"jest": {
"testRegex": "-test\\.js",
"setupFiles": [
"./test/setup.js"
],
"setupFilesAfterEnv": [
"./test/setupAfterEnv.js"
],
"roots": [
"<rootDir>/test"
]
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.0",
"@restart/hooks": "^0.3.22",
"@semantic-release/changelog": "^5.0.1",
"@semantic-release/git": "^9.0.0",
"@semantic-release/github": "^7.0.5",
"@semantic-release/npm": "^7.0.5",
"@storybook/addon-actions": "^6.3.4",
"@storybook/react": "^6.3.4",
"@testing-library/react": "alpha",
"@typescript-eslint/eslint-plugin": "^4.26.1",
"astroturf": "^0.10.4",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"babel-preset-jason": "^6.2.0",
"cherry-pick": "^0.5.0",
"cross-env": "^7.0.2",
"eslint": "^7.28.0",
"eslint-config-jason": "^8.1.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^25.3.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"release-script": "^1.0.2",
"rimraf": "^3.0.2",
"rollup": "^2.6.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-size-snapshot": "^0.11.0",
"rollup-plugin-terser": "^5.3.0",
"semantic-release": "^17.0.6",
"semantic-release-alt-publish-dir": "^3.0.0",
"typescript": "^4.3.2",
"webpack-atoms": "14.0.0"
},
"release": {
"pkgRoot": "lib",
"verifyConditions": [
"@semantic-release/changelog",
"semantic-release-alt-publish-dir",
"@semantic-release/git",
"@semantic-release/github"
],
"prepare": [
"@semantic-release/changelog",
"semantic-release-alt-publish-dir",
"@semantic-release/npm",
"@semantic-release/git"
]
},
"browserify": {
"transform": [
"loose-envify"
]
},
"sideEffects": false
}
================================================
FILE: prettier.config.js
================================================
module.exports = {
singleQuote: true,
};
================================================
FILE: rollup.config.js
================================================
import nodeResolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import commonjs from 'rollup-plugin-commonjs';
import replace from 'rollup-plugin-replace';
import { sizeSnapshot } from 'rollup-plugin-size-snapshot';
import { terser } from 'rollup-plugin-terser';
const input = './src/index.js';
const name = 'ReactTransitionGroup';
const globals = {
react: 'React',
'react-dom': 'ReactDOM',
};
const babelOptions = {
exclude: /node_modules/,
runtimeHelpers: true,
};
const commonjsOptions = {
include: /node_modules/,
namedExports: {
'prop-types': ['object', 'oneOfType', 'element', 'bool', 'func'],
},
};
export default [
{
input,
output: {
file: './lib/dist/react-transition-group.js',
format: 'umd',
name,
globals,
},
external: Object.keys(globals),
plugins: [
nodeResolve(),
babel(babelOptions),
commonjs(commonjsOptions),
replace({ 'process.env.NODE_ENV': JSON.stringify('development') }),
sizeSnapshot(),
],
},
{
input,
output: {
file: './lib/dist/react-transition-group.min.js',
format: 'umd',
name,
globals,
},
external: Object.keys(globals),
plugins: [
nodeResolve(),
babel(babelOptions),
commonjs(commonjsOptions),
replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
sizeSnapshot(),
terser(),
],
},
];
================================================
FILE: src/CSSTransition.js
================================================
import PropTypes from 'prop-types';
import addOneClass from 'dom-helpers/addClass';
import removeOneClass from 'dom-helpers/removeClass';
import React from 'react';
import Transition from './Transition';
import { classNamesShape } from './utils/PropTypes';
import { forceReflow } from './utils/reflow';
const addClass = (node, classes) =>
node && classes && classes.split(' ').forEach((c) => addOneClass(node, c));
const removeClass = (node, classes) =>
node && classes && classes.split(' ').forEach((c) => removeOneClass(node, c));
/**
* A transition component inspired by the excellent
* [ng-animate](https://docs.angularjs.org/api/ngAnimate) library, you should
* use it if you're using CSS transitions or animations. It's built upon the
* [`Transition`](https://reactcommunity.org/react-transition-group/transition)
* component, so it inherits all of its props.
*
* `CSSTransition` applies a pair of class names during the `appear`, `enter`,
* and `exit` states of the transition. The first class is applied and then a
* second `*-active` class in order to activate the CSS transition. After the
* transition, matching `*-done` class names are applied to persist the
* transition state.
*
* ```jsx
* function App() {
* const [inProp, setInProp] = useState(false);
* const nodeRef = useRef(null);
* return (
* <div>
* <CSSTransition nodeRef={nodeRef} in={inProp} timeout={200} classNames="my-node">
* <div ref={nodeRef}>
* {"I'll receive my-node-* classes"}
* </div>
* </CSSTransition>
* <button type="button" onClick={() => setInProp(true)}>
* Click to Enter
* </button>
* </div>
* );
* }
* ```
*
* When the `in` prop is set to `true`, the child component will first receive
* the class `example-enter`, then the `example-enter-active` will be added in
* the next tick. `CSSTransition` [forces a
* reflow](https://github.com/reactjs/react-transition-group/blob/5007303e729a74be66a21c3e2205e4916821524b/src/CSSTransition.js#L208-L215)
* between before adding the `example-enter-active`. This is an important trick
* because it allows us to transition between `example-enter` and
* `example-enter-active` even though they were added immediately one after
* another. Most notably, this is what makes it possible for us to animate
* _appearance_.
*
* ```css
* .my-node-enter {
* opacity: 0;
* }
* .my-node-enter-active {
* opacity: 1;
* transition: opacity 200ms;
* }
* .my-node-exit {
* opacity: 1;
* }
* .my-node-exit-active {
* opacity: 0;
* transition: opacity 200ms;
* }
* ```
*
* `*-active` classes represent which styles you want to animate **to**, so it's
* important to add `transition` declaration only to them, otherwise transitions
* might not behave as intended! This might not be obvious when the transitions
* are symmetrical, i.e. when `*-enter-active` is the same as `*-exit`, like in
* the example above (minus `transition`), but it becomes apparent in more
* complex transitions.
*
* **Note**: If you're using the
* [`appear`](http://reactcommunity.org/react-transition-group/transition#Transition-prop-appear)
* prop, make sure to define styles for `.appear-*` classes as well.
*/
class CSSTransition extends React.Component {
static defaultProps = {
classNames: '',
};
appliedClasses = {
appear: {},
enter: {},
exit: {},
};
onEnter = (maybeNode, maybeAppearing) => {
const [node, appearing] = this.resolveArguments(maybeNode, maybeAppearing);
this.removeClasses(node, 'exit');
this.addClass(node, appearing ? 'appear' : 'enter', 'base');
if (this.props.onEnter) {
this.props.onEnter(maybeNode, maybeAppearing);
}
};
onEntering = (maybeNode, maybeAppearing) => {
const [node, appearing] = this.resolveArguments(maybeNode, maybeAppearing);
const type = appearing ? 'appear' : 'enter';
this.addClass(node, type, 'active');
if (this.props.onEntering) {
this.props.onEntering(maybeNode, maybeAppearing);
}
};
onEntered = (maybeNode, maybeAppearing) => {
const [node, appearing] = this.resolveArguments(maybeNode, maybeAppearing);
const type = appearing ? 'appear' : 'enter';
this.removeClasses(node, type);
this.addClass(node, type, 'done');
if (this.props.onEntered) {
this.props.onEntered(maybeNode, maybeAppearing);
}
};
onExit = (maybeNode) => {
const [node] = this.resolveArguments(maybeNode);
this.removeClasses(node, 'appear');
this.removeClasses(node, 'enter');
this.addClass(node, 'exit', 'base');
if (this.props.onExit) {
this.props.onExit(maybeNode);
}
};
onExiting = (maybeNode) => {
const [node] = this.resolveArguments(maybeNode);
this.addClass(node, 'exit', 'active');
if (this.props.onExiting) {
this.props.onExiting(maybeNode);
}
};
onExited = (maybeNode) => {
const [node] = this.resolveArguments(maybeNode);
this.removeClasses(node, 'exit');
this.addClass(node, 'exit', 'done');
if (this.props.onExited) {
this.props.onExited(maybeNode);
}
};
// when prop `nodeRef` is provided `node` is excluded
resolveArguments = (maybeNode, maybeAppearing) =>
this.props.nodeRef
? [this.props.nodeRef.current, maybeNode] // here `maybeNode` is actually `appearing`
: [maybeNode, maybeAppearing]; // `findDOMNode` was used
getClassNames = (type) => {
const { classNames } = this.props;
const isStringClassNames = typeof classNames === 'string';
const prefix = isStringClassNames && classNames ? `${classNames}-` : '';
let baseClassName = isStringClassNames
? `${prefix}${type}`
: classNames[type];
let activeClassName = isStringClassNames
? `${baseClassName}-active`
: classNames[`${type}Active`];
let doneClassName = isStringClassNames
? `${baseClassName}-done`
: classNames[`${type}Done`];
return {
baseClassName,
activeClassName,
doneClassName,
};
};
addClass(node, type, phase) {
let className = this.getClassNames(type)[`${phase}ClassName`];
const { doneClassName } = this.getClassNames('enter');
if (type === 'appear' && phase === 'done' && doneClassName) {
className += ` ${doneClassName}`;
}
// This is to force a repaint,
// which is necessary in order to transition styles when adding a class name.
if (phase === 'active') {
if (node) forceReflow(node);
}
if (className) {
this.appliedClasses[type][phase] = className;
addClass(node, className);
}
}
removeClasses(node, type) {
const {
base: baseClassName,
active: activeClassName,
done: doneClassName,
} = this.appliedClasses[type];
this.appliedClasses[type] = {};
if (baseClassName) {
removeClass(node, baseClassName);
}
if (activeClassName) {
removeClass(node, activeClassName);
}
if (doneClassName) {
removeClass(node, doneClassName);
}
}
render() {
const { classNames: _, ...props } = this.props;
return (
<Transition
{...props}
onEnter={this.onEnter}
onEntered={this.onEntered}
onEntering={this.onEntering}
onExit={this.onExit}
onExiting={this.onExiting}
onExited={this.onExited}
/>
);
}
}
CSSTransition.propTypes = {
...Transition.propTypes,
/**
* The animation classNames applied to the component as it appears, enters,
* exits or has finished the transition. A single name can be provided, which
* will be suffixed for each stage, e.g. `classNames="fade"` applies:
*
* - `fade-appear`, `fade-appear-active`, `fade-appear-done`
* - `fade-enter`, `fade-enter-active`, `fade-enter-done`
* - `fade-exit`, `fade-exit-active`, `fade-exit-done`
*
* A few details to note about how these classes are applied:
*
* 1. They are _joined_ with the ones that are already defined on the child
* component, so if you want to add some base styles, you can use
* `className` without worrying that it will be overridden.
*
* 2. If the transition component mounts with `in={false}`, no classes are
* applied yet. You might be expecting `*-exit-done`, but if you think
* about it, a component cannot finish exiting if it hasn't entered yet.
*
* 2. `fade-appear-done` and `fade-enter-done` will _both_ be applied. This
* allows you to define different behavior for when appearing is done and
* when regular entering is done, using selectors like
* `.fade-enter-done:not(.fade-appear-done)`. For example, you could apply
* an epic entrance animation when element first appears in the DOM using
* [Animate.css](https://daneden.github.io/animate.css/). Otherwise you can
* simply use `fade-enter-done` for defining both cases.
*
* Each individual classNames can also be specified independently like:
*
* ```js
* classNames={{
* appear: 'my-appear',
* appearActive: 'my-active-appear',
* appearDone: 'my-done-appear',
* enter: 'my-enter',
* enterActive: 'my-active-enter',
* enterDone: 'my-done-enter',
* exit: 'my-exit',
* exitActive: 'my-active-exit',
* exitDone: 'my-done-exit',
* }}
* ```
*
* If you want to set these classes using CSS Modules:
*
* ```js
* import styles from './styles.css';
* ```
*
* you might want to use camelCase in your CSS file, that way could simply
* spread them instead of listing them one by one:
*
* ```js
* classNames={{ ...styles }}
* ```
*
* @type {string | {
* appear?: string,
* appearActive?: string,
* appearDone?: string,
* enter?: string,
* enterActive?: string,
* enterDone?: string,
* exit?: string,
* exitActive?: string,
* exitDone?: string,
* }}
*/
classNames: classNamesShape,
/**
* A `<Transition>` callback fired immediately after the 'enter' or 'appear' class is
* applied.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed, so `isAppearing` is being passed as the first argument.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onEnter: PropTypes.func,
/**
* A `<Transition>` callback fired immediately after the 'enter-active' or
* 'appear-active' class is applied.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed, so `isAppearing` is being passed as the first argument.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onEntering: PropTypes.func,
/**
* A `<Transition>` callback fired immediately after the 'enter' or
* 'appear' classes are **removed** and the `done` class is added to the DOM node.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed, so `isAppearing` is being passed as the first argument.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onEntered: PropTypes.func,
/**
* A `<Transition>` callback fired immediately after the 'exit' class is
* applied.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed
*
* @type Function(node: HtmlElement)
*/
onExit: PropTypes.func,
/**
* A `<Transition>` callback fired immediately after the 'exit-active' is applied.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed
*
* @type Function(node: HtmlElement)
*/
onExiting: PropTypes.func,
/**
* A `<Transition>` callback fired immediately after the 'exit' classes
* are **removed** and the `exit-done` class is added to the DOM node.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed
*
* @type Function(node: HtmlElement)
*/
onExited: PropTypes.func,
};
export default CSSTransition;
================================================
FILE: src/ReplaceTransition.js
================================================
import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import TransitionGroup from './TransitionGroup';
/**
* The `<ReplaceTransition>` component is a specialized `Transition` component
* that animates between two children.
*
* ```jsx
* <ReplaceTransition in>
* <Fade><div>I appear first</div></Fade>
* <Fade><div>I replace the above</div></Fade>
* </ReplaceTransition>
* ```
*/
class ReplaceTransition extends React.Component {
handleEnter = (...args) => this.handleLifecycle('onEnter', 0, args);
handleEntering = (...args) => this.handleLifecycle('onEntering', 0, args);
handleEntered = (...args) => this.handleLifecycle('onEntered', 0, args);
handleExit = (...args) => this.handleLifecycle('onExit', 1, args);
handleExiting = (...args) => this.handleLifecycle('onExiting', 1, args);
handleExited = (...args) => this.handleLifecycle('onExited', 1, args);
handleLifecycle(handler, idx, originalArgs) {
const { children } = this.props;
const child = React.Children.toArray(children)[idx];
if (child.props[handler]) child.props[handler](...originalArgs);
if (this.props[handler]) {
const maybeNode = child.props.nodeRef
? undefined
: ReactDOM.findDOMNode(this);
this.props[handler](maybeNode);
}
}
render() {
const { children, in: inProp, ...props } = this.props;
const [first, second] = React.Children.toArray(children);
delete props.onEnter;
delete props.onEntering;
delete props.onEntered;
delete props.onExit;
delete props.onExiting;
delete props.onExited;
return (
<TransitionGroup {...props}>
{inProp
? React.cloneElement(first, {
key: 'first',
onEnter: this.handleEnter,
onEntering: this.handleEntering,
onEntered: this.handleEntered,
})
: React.cloneElement(second, {
key: 'second',
onEnter: this.handleExit,
onEntering: this.handleExiting,
onEntered: this.handleExited,
})}
</TransitionGroup>
);
}
}
ReplaceTransition.propTypes = {
in: PropTypes.bool.isRequired,
children(props, propName) {
if (React.Children.count(props[propName]) !== 2)
return new Error(
`"${propName}" must be exactly two transition components.`
);
return null;
},
};
export default ReplaceTransition;
================================================
FILE: src/SwitchTransition.js
================================================
import React from 'react';
import PropTypes from 'prop-types';
import { ENTERED, ENTERING, EXITING } from './Transition';
import TransitionGroupContext from './TransitionGroupContext';
function areChildrenDifferent(oldChildren, newChildren) {
if (oldChildren === newChildren) return false;
if (
React.isValidElement(oldChildren) &&
React.isValidElement(newChildren) &&
oldChildren.key != null &&
oldChildren.key === newChildren.key
) {
return false;
}
return true;
}
/**
* Enum of modes for SwitchTransition component
* @enum { string }
*/
export const modes = {
out: 'out-in',
in: 'in-out',
};
const callHook =
(element, name, cb) =>
(...args) => {
element.props[name] && element.props[name](...args);
cb();
};
const leaveRenders = {
[modes.out]: ({ current, changeState }) =>
React.cloneElement(current, {
in: false,
onExited: callHook(current, 'onExited', () => {
changeState(ENTERING, null);
}),
}),
[modes.in]: ({ current, changeState, children }) => [
current,
React.cloneElement(children, {
in: true,
onEntered: callHook(children, 'onEntered', () => {
changeState(ENTERING);
}),
}),
],
};
const enterRenders = {
[modes.out]: ({ children, changeState }) =>
React.cloneElement(children, {
in: true,
onEntered: callHook(children, 'onEntered', () => {
changeState(ENTERED, React.cloneElement(children, { in: true }));
}),
}),
[modes.in]: ({ current, children, changeState }) => [
React.cloneElement(current, {
in: false,
onExited: callHook(current, 'onExited', () => {
changeState(ENTERED, React.cloneElement(children, { in: true }));
}),
}),
React.cloneElement(children, {
in: true,
}),
],
};
/**
* A transition component inspired by the [vue transition modes](https://vuejs.org/v2/guide/transitions.html#Transition-Modes).
* You can use it when you want to control the render between state transitions.
* Based on the selected mode and the child's key which is the `Transition` or `CSSTransition` component, the `SwitchTransition` makes a consistent transition between them.
*
* If the `out-in` mode is selected, the `SwitchTransition` waits until the old child leaves and then inserts a new child.
* If the `in-out` mode is selected, the `SwitchTransition` inserts a new child first, waits for the new child to enter and then removes the old child.
*
* **Note**: If you want the animation to happen simultaneously
* (that is, to have the old child removed and a new child inserted **at the same time**),
* you should use
* [`TransitionGroup`](https://reactcommunity.org/react-transition-group/transition-group)
* instead.
*
* ```jsx
* function App() {
* const [state, setState] = useState(false);
* const helloRef = useRef(null);
* const goodbyeRef = useRef(null);
* const nodeRef = state ? goodbyeRef : helloRef;
* return (
* <SwitchTransition>
* <CSSTransition
* key={state ? "Goodbye, world!" : "Hello, world!"}
* nodeRef={nodeRef}
* addEndListener={(node, done) => node.addEventListener("transitionend", done, false)}
* classNames='fade'
* >
* <button ref={nodeRef} onClick={() => setState(state => !state)}>
* {state ? "Goodbye, world!" : "Hello, world!"}
* </button>
* </CSSTransition>
* </SwitchTransition>
* );
* }
* ```
*
* ```css
* .fade-enter{
* opacity: 0;
* }
* .fade-exit{
* opacity: 1;
* }
* .fade-enter-active{
* opacity: 1;
* }
* .fade-exit-active{
* opacity: 0;
* }
* .fade-enter-active,
* .fade-exit-active{
* transition: opacity 500ms;
* }
* ```
*/
class SwitchTransition extends React.Component {
state = {
status: ENTERED,
current: null,
};
appeared = false;
componentDidMount() {
this.appeared = true;
}
static getDerivedStateFromProps(props, state) {
if (props.children == null) {
return {
current: null,
};
}
if (state.status === ENTERING && props.mode === modes.in) {
return {
status: ENTERING,
};
}
if (state.current && areChildrenDifferent(state.current, props.children)) {
return {
status: EXITING,
};
}
return {
current: React.cloneElement(props.children, {
in: true,
}),
};
}
changeState = (status, current = this.state.current) => {
this.setState({
status,
current,
});
};
render() {
const {
props: { children, mode },
state: { status, current },
} = this;
const data = { children, current, changeState: this.changeState, status };
let component;
switch (status) {
case ENTERING:
component = enterRenders[mode](data);
break;
case EXITING:
component = leaveRenders[mode](data);
break;
case ENTERED:
component = current;
}
return (
<TransitionGroupContext.Provider value={{ isMounting: !this.appeared }}>
{component}
</TransitionGroupContext.Provider>
);
}
}
SwitchTransition.propTypes = {
/**
* Transition modes.
* `out-in`: Current element transitions out first, then when complete, the new element transitions in.
* `in-out`: New element transitions in first, then when complete, the current element transitions out.
*
* @type {'out-in'|'in-out'}
*/
mode: PropTypes.oneOf([modes.in, modes.out]),
/**
* Any `Transition` or `CSSTransition` component.
*/
children: PropTypes.oneOfType([PropTypes.element.isRequired]),
};
SwitchTransition.defaultProps = {
mode: modes.out,
};
export default SwitchTransition;
================================================
FILE: src/Transition.js
================================================
import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import config from './config';
import { timeoutsShape } from './utils/PropTypes';
import TransitionGroupContext from './TransitionGroupContext';
import { forceReflow } from './utils/reflow';
export const UNMOUNTED = 'unmounted';
export const EXITED = 'exited';
export const ENTERING = 'entering';
export const ENTERED = 'entered';
export const EXITING = 'exiting';
/**
* The Transition component lets you describe a transition from one component
* state to another _over time_ with a simple declarative API. Most commonly
* it's used to animate the mounting and unmounting of a component, but can also
* be used to describe in-place transition states as well.
*
* ---
*
* **Note**: `Transition` is a platform-agnostic base component. If you're using
* transitions in CSS, you'll probably want to use
* [`CSSTransition`](https://reactcommunity.org/react-transition-group/css-transition)
* instead. It inherits all the features of `Transition`, but contains
* additional features necessary to play nice with CSS transitions (hence the
* name of the component).
*
* ---
*
* By default the `Transition` component does not alter the behavior of the
* component it renders, it only tracks "enter" and "exit" states for the
* components. It's up to you to give meaning and effect to those states. For
* example we can add styles to a component when it enters or exits:
*
* ```jsx
* import { Transition } from 'react-transition-group';
* import { useRef } from 'react';
*
* const duration = 300;
*
* const defaultStyle = {
* transition: `opacity ${duration}ms ease-in-out`,
* opacity: 0,
* }
*
* const transitionStyles = {
* entering: { opacity: 1 },
* entered: { opacity: 1 },
* exiting: { opacity: 0 },
* exited: { opacity: 0 },
* };
*
* function Fade({ in: inProp }) {
* const nodeRef = useRef(null);
* return (
* <Transition nodeRef={nodeRef} in={inProp} timeout={duration}>
* {state => (
* <div ref={nodeRef} style={{
* ...defaultStyle,
* ...transitionStyles[state]
* }}>
* I'm a fade Transition!
* </div>
* )}
* </Transition>
* );
* }
* ```
*
* There are 4 main states a Transition can be in:
* - `'entering'`
* - `'entered'`
* - `'exiting'`
* - `'exited'`
*
* Transition state is toggled via the `in` prop. When `true` the component
* begins the "Enter" stage. During this stage, the component will shift from
* its current transition state, to `'entering'` for the duration of the
* transition and then to the `'entered'` stage once it's complete. Let's take
* the following example (we'll use the
* [useState](https://reactjs.org/docs/hooks-reference.html#usestate) hook):
*
* ```jsx
* import { Transition } from 'react-transition-group';
* import { useState, useRef } from 'react';
*
* function App() {
* const [inProp, setInProp] = useState(false);
* const nodeRef = useRef(null);
* return (
* <div>
* <Transition nodeRef={nodeRef} in={inProp} timeout={500}>
* {state => (
* // ...
* )}
* </Transition>
* <button onClick={() => setInProp(true)}>
* Click to Enter
* </button>
* </div>
* );
* }
* ```
*
* When the button is clicked the component will shift to the `'entering'` state
* and stay there for 500ms (the value of `timeout`) before it finally switches
* to `'entered'`.
*
* When `in` is `false` the same thing happens except the state moves from
* `'exiting'` to `'exited'`.
*/
class Transition extends React.Component {
static contextType = TransitionGroupContext;
constructor(props, context) {
super(props, context);
let parentGroup = context;
// In the context of a TransitionGroup all enters are really appears
let appear =
parentGroup && !parentGroup.isMounting ? props.enter : props.appear;
let initialStatus;
this.appearStatus = null;
if (props.in) {
if (appear) {
initialStatus = EXITED;
this.appearStatus = ENTERING;
} else {
initialStatus = ENTERED;
}
} else {
if (props.unmountOnExit || props.mountOnEnter) {
initialStatus = UNMOUNTED;
} else {
initialStatus = EXITED;
}
}
this.state = { status: initialStatus };
this.nextCallback = null;
}
static getDerivedStateFromProps({ in: nextIn }, prevState) {
if (nextIn && prevState.status === UNMOUNTED) {
return { status: EXITED };
}
return null;
}
// getSnapshotBeforeUpdate(prevProps) {
// let nextStatus = null
// if (prevProps !== this.props) {
// const { status } = this.state
// if (this.props.in) {
// if (status !== ENTERING && status !== ENTERED) {
// nextStatus = ENTERING
// }
// } else {
// if (status === ENTERING || status === ENTERED) {
// nextStatus = EXITING
// }
// }
// }
// return { nextStatus }
// }
componentDidMount() {
this.updateStatus(true, this.appearStatus);
}
componentDidUpdate(prevProps) {
let nextStatus = null;
if (prevProps !== this.props) {
const { status } = this.state;
if (this.props.in) {
if (status !== ENTERING && status !== ENTERED) {
nextStatus = ENTERING;
}
} else {
if (status === ENTERING || status === ENTERED) {
nextStatus = EXITING;
}
}
}
this.updateStatus(false, nextStatus);
}
componentWillUnmount() {
this.cancelNextCallback();
}
getTimeouts() {
const { timeout } = this.props;
let exit, enter, appear;
exit = enter = appear = timeout;
if (timeout != null && typeof timeout !== 'number') {
exit = timeout.exit;
enter = timeout.enter;
// TODO: remove fallback for next major
appear = timeout.appear !== undefined ? timeout.appear : enter;
}
return { exit, enter, appear };
}
updateStatus(mounting = false, nextStatus) {
if (nextStatus !== null) {
// nextStatus will always be ENTERING or EXITING.
this.cancelNextCallback();
if (nextStatus === ENTERING) {
if (this.props.unmountOnExit || this.props.mountOnEnter) {
const node = this.props.nodeRef
? this.props.nodeRef.current
: ReactDOM.findDOMNode(this);
// https://github.com/reactjs/react-transition-group/pull/749
// With unmountOnExit or mountOnEnter, the enter animation should happen at the transition between `exited` and `entering`.
// To make the animation happen, we have to separate each rendering and avoid being processed as batched.
if (node) forceReflow(node);
}
this.performEnter(mounting);
} else {
this.performExit();
}
} else if (this.props.unmountOnExit && this.state.status === EXITED) {
this.setState({ status: UNMOUNTED });
}
}
performEnter(mounting) {
const { enter } = this.props;
const appearing = this.context ? this.context.isMounting : mounting;
const [maybeNode, maybeAppearing] = this.props.nodeRef
? [appearing]
: [ReactDOM.findDOMNode(this), appearing];
const timeouts = this.getTimeouts();
const enterTimeout = appearing ? timeouts.appear : timeouts.enter;
// no enter animation skip right to ENTERED
// if we are mounting and running this it means appear _must_ be set
if ((!mounting && !enter) || config.disabled) {
this.safeSetState({ status: ENTERED }, () => {
this.props.onEntered(maybeNode);
});
return;
}
this.props.onEnter(maybeNode, maybeAppearing);
this.safeSetState({ status: ENTERING }, () => {
this.props.onEntering(maybeNode, maybeAppearing);
this.onTransitionEnd(enterTimeout, () => {
this.safeSetState({ status: ENTERED }, () => {
this.props.onEntered(maybeNode, maybeAppearing);
});
});
});
}
performExit() {
const { exit } = this.props;
const timeouts = this.getTimeouts();
const maybeNode = this.props.nodeRef
? undefined
: ReactDOM.findDOMNode(this);
// no exit animation skip right to EXITED
if (!exit || config.disabled) {
this.safeSetState({ status: EXITED }, () => {
this.props.onExited(maybeNode);
});
return;
}
this.props.onExit(maybeNode);
this.safeSetState({ status: EXITING }, () => {
this.props.onExiting(maybeNode);
this.onTransitionEnd(timeouts.exit, () => {
this.safeSetState({ status: EXITED }, () => {
this.props.onExited(maybeNode);
});
});
});
}
cancelNextCallback() {
if (this.nextCallback !== null) {
this.nextCallback.cancel();
this.nextCallback = null;
}
}
safeSetState(nextState, callback) {
// This shouldn't be necessary, but there are weird race conditions with
// setState callbacks and unmounting in testing, so always make sure that
// we can cancel any pending setState callbacks after we unmount.
callback = this.setNextCallback(callback);
this.setState(nextState, callback);
}
setNextCallback(callback) {
let active = true;
this.nextCallback = (event) => {
if (active) {
active = false;
this.nextCallback = null;
callback(event);
}
};
this.nextCallback.cancel = () => {
active = false;
};
return this.nextCallback;
}
onTransitionEnd(timeout, handler) {
this.setNextCallback(handler);
const node = this.props.nodeRef
? this.props.nodeRef.current
: ReactDOM.findDOMNode(this);
const doesNotHaveTimeoutOrListener =
timeout == null && !this.props.addEndListener;
if (!node || doesNotHaveTimeoutOrListener) {
setTimeout(this.nextCallback, 0);
return;
}
if (this.props.addEndListener) {
const [maybeNode, maybeNextCallback] = this.props.nodeRef
? [this.nextCallback]
: [node, this.nextCallback];
this.props.addEndListener(maybeNode, maybeNextCallback);
}
if (timeout != null) {
setTimeout(this.nextCallback, timeout);
}
}
render() {
const status = this.state.status;
if (status === UNMOUNTED) {
return null;
}
const {
children,
// filter props for `Transition`
in: _in,
mountOnEnter: _mountOnEnter,
unmountOnExit: _unmountOnExit,
appear: _appear,
enter: _enter,
exit: _exit,
timeout: _timeout,
addEndListener: _addEndListener,
onEnter: _onEnter,
onEntering: _onEntering,
onEntered: _onEntered,
onExit: _onExit,
onExiting: _onExiting,
onExited: _onExited,
nodeRef: _nodeRef,
...childProps
} = this.props;
return (
// allows for nested Transitions
<TransitionGroupContext.Provider value={null}>
{typeof children === 'function'
? children(status, childProps)
: React.cloneElement(React.Children.only(children), childProps)}
</TransitionGroupContext.Provider>
);
}
}
Transition.propTypes = {
/**
* A React reference to the DOM element that needs to transition:
* https://stackoverflow.com/a/51127130/4671932
*
* - This prop is optional, but recommended in order to avoid defaulting to
* [`ReactDOM.findDOMNode`](https://reactjs.org/docs/react-dom.html#finddomnode),
* which is deprecated in `StrictMode`
* - When `nodeRef` prop is used, `node` is not passed to callback functions
* (e.g. `onEnter`) because user already has direct access to the node.
* - When changing `key` prop of `Transition` in a `TransitionGroup` a new
* `nodeRef` need to be provided to `Transition` with changed `key` prop
* (see
* [test/CSSTransition-test.js](https://github.com/reactjs/react-transition-group/blob/13435f897b3ab71f6e19d724f145596f5910581c/test/CSSTransition-test.js#L362-L437)).
*/
nodeRef: PropTypes.shape({
current:
typeof Element === 'undefined'
? PropTypes.any
: (propValue, key, componentName, location, propFullName, secret) => {
const value = propValue[key];
return PropTypes.instanceOf(
value && 'ownerDocument' in value
? value.ownerDocument.defaultView.Element
: Element
)(propValue, key, componentName, location, propFullName, secret);
},
}),
/**
* A `function` child can be used instead of a React element. This function is
* called with the current transition status (`'entering'`, `'entered'`,
* `'exiting'`, `'exited'`), which can be used to apply context
* specific props to a component.
*
* ```jsx
* <Transition nodeRef={nodeRef} in={this.state.in} timeout={150}>
* {state => (
* <MyComponent ref={nodeRef} className={`fade fade-${state}`} />
* )}
* </Transition>
* ```
*/
children: PropTypes.oneOfType([
PropTypes.func.isRequired,
PropTypes.element.isRequired,
]).isRequired,
/**
* Show the component; triggers the enter or exit states
*/
in: PropTypes.bool,
/**
* By default the child component is mounted immediately along with
* the parent `Transition` component. If you want to "lazy mount" the component on the
* first `in={true}` you can set `mountOnEnter`. After the first enter transition the component will stay
* mounted, even on "exited", unless you also specify `unmountOnExit`.
*/
mountOnEnter: PropTypes.bool,
/**
* By default the child component stays mounted after it reaches the `'exited'` state.
* Set `unmountOnExit` if you'd prefer to unmount the component after it finishes exiting.
*/
unmountOnExit: PropTypes.bool,
/**
* By default the child component does not perform the enter transition when
* it first mounts, regardless of the value of `in`. If you want this
* behavior, set both `appear` and `in` to `true`.
*
* > **Note**: there are no special appear states like `appearing`/`appeared`, this prop
* > only adds an additional enter transition. However, in the
* > `<CSSTransition>` component that first enter transition does result in
* > additional `.appear-*` classes, that way you can choose to style it
* > differently.
*/
appear: PropTypes.bool,
/**
* Enable or disable enter transitions.
*/
enter: PropTypes.bool,
/**
* Enable or disable exit transitions.
*/
exit: PropTypes.bool,
/**
* The duration of the transition, in milliseconds.
* Required unless `addEndListener` is provided.
*
* You may specify a single timeout for all transitions:
*
* ```jsx
* timeout={500}
* ```
*
* or individually:
*
* ```jsx
* timeout={{
* appear: 500,
* enter: 300,
* exit: 500,
* }}
* ```
*
* - `appear` defaults to the value of `enter`
* - `enter` defaults to `0`
* - `exit` defaults to `0`
*
* @type {number | { enter?: number, exit?: number, appear?: number }}
*/
timeout: (props, ...args) => {
let pt = timeoutsShape;
if (!props.addEndListener) pt = pt.isRequired;
return pt(props, ...args);
},
/**
* Add a custom transition end trigger. Called with the transitioning
* DOM node and a `done` callback. Allows for more fine grained transition end
* logic. Timeouts are still used as a fallback if provided.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed, so `done` is being passed as the first argument.
*
* ```jsx
* addEndListener={(node, done) => {
* // use the css transitionend event to mark the finish of a transition
* node.addEventListener('transitionend', done, false);
* }}
* ```
*/
addEndListener: PropTypes.func,
/**
* Callback fired before the "entering" status is applied. An extra parameter
* `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed, so `isAppearing` is being passed as the first argument.
*
* @type Function(node: HtmlElement, isAppearing: bool) -> void
*/
onEnter: PropTypes.func,
/**
* Callback fired after the "entering" status is applied. An extra parameter
* `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed, so `isAppearing` is being passed as the first argument.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onEntering: PropTypes.func,
/**
* Callback fired after the "entered" status is applied. An extra parameter
* `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed, so `isAppearing` is being passed as the first argument.
*
* @type Function(node: HtmlElement, isAppearing: bool) -> void
*/
onEntered: PropTypes.func,
/**
* Callback fired before the "exiting" status is applied.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed.
*
* @type Function(node: HtmlElement) -> void
*/
onExit: PropTypes.func,
/**
* Callback fired after the "exiting" status is applied.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed.
*
* @type Function(node: HtmlElement) -> void
*/
onExiting: PropTypes.func,
/**
* Callback fired after the "exited" status is applied.
*
* **Note**: when `nodeRef` prop is passed, `node` is not passed
*
* @type Function(node: HtmlElement) -> void
*/
onExited: PropTypes.func,
};
// Name the function so it is clearer in the documentation
function noop() {}
Transition.defaultProps = {
in: false,
mountOnEnter: false,
unmountOnExit: false,
appear: false,
enter: true,
exit: true,
onEnter: noop,
onEntering: noop,
onEntered: noop,
onExit: noop,
onExiting: noop,
onExited: noop,
};
Transition.UNMOUNTED = UNMOUNTED;
Transition.EXITED = EXITED;
Transition.ENTERING = ENTERING;
Transition.ENTERED = ENTERED;
Transition.EXITING = EXITING;
export default Transition;
================================================
FILE: src/TransitionGroup.js
================================================
import PropTypes from 'prop-types';
import React from 'react';
import TransitionGroupContext from './TransitionGroupContext';
import {
getChildMapping,
getInitialChildMapping,
getNextChildMapping,
} from './utils/ChildMapping';
const values = Object.values || ((obj) => Object.keys(obj).map((k) => obj[k]));
const defaultProps = {
component: 'div',
childFactory: (child) => child,
};
/**
* The `<TransitionGroup>` component manages a set of transition components
* (`<Transition>` and `<CSSTransition>`) in a list. Like with the transition
* components, `<TransitionGroup>` is a state machine for managing the mounting
* and unmounting of components over time.
*
* Consider the example below. As items are removed or added to the TodoList the
* `in` prop is toggled automatically by the `<TransitionGroup>`.
*
* Note that `<TransitionGroup>` does not define any animation behavior!
* Exactly _how_ a list item animates is up to the individual transition
* component. This means you can mix and match animations across different list
* items.
*/
class TransitionGroup extends React.Component {
constructor(props, context) {
super(props, context);
const handleExited = this.handleExited.bind(this);
// Initial children should all be entering, dependent on appear
this.state = {
contextValue: { isMounting: true },
handleExited,
firstRender: true,
};
}
componentDidMount() {
this.mounted = true;
this.setState({
contextValue: { isMounting: false },
});
}
componentWillUnmount() {
this.mounted = false;
}
static getDerivedStateFromProps(
nextProps,
{ children: prevChildMapping, handleExited, firstRender }
) {
return {
children: firstRender
? getInitialChildMapping(nextProps, handleExited)
: getNextChildMapping(nextProps, prevChildMapping, handleExited),
firstRender: false,
};
}
// node is `undefined` when user provided `nodeRef` prop
handleExited(child, node) {
let currentChildMapping = getChildMapping(this.props.children);
if (child.key in currentChildMapping) return;
if (child.props.onExited) {
child.props.onExited(node);
}
if (this.mounted) {
this.setState((state) => {
let children = { ...state.children };
delete children[child.key];
return { children };
});
}
}
render() {
const { component: Component, childFactory, ...props } = this.props;
const { contextValue } = this.state;
const children = values(this.state.children).map(childFactory);
delete props.appear;
delete props.enter;
delete props.exit;
if (Component === null) {
return (
<TransitionGroupContext.Provider value={contextValue}>
{children}
</TransitionGroupContext.Provider>
);
}
return (
<TransitionGroupContext.Provider value={contextValue}>
<Component {...props}>{children}</Component>
</TransitionGroupContext.Provider>
);
}
}
TransitionGroup.propTypes = {
/**
* `<TransitionGroup>` renders a `<div>` by default. You can change this
* behavior by providing a `component` prop.
* If you use React v16+ and would like to avoid a wrapping `<div>` element
* you can pass in `component={null}`. This is useful if the wrapping div
* borks your css styles.
*/
component: PropTypes.any,
/**
* A set of `<Transition>` components, that are toggled `in` and out as they
* leave. the `<TransitionGroup>` will inject specific transition props, so
* remember to spread them through if you are wrapping the `<Transition>` as
* with our `<Fade>` example.
*
* While this component is meant for multiple `Transition` or `CSSTransition`
* children, sometimes you may want to have a single transition child with
* content that you want to be transitioned out and in when you change it
* (e.g. routes, images etc.) In that case you can change the `key` prop of
* the transition child as you change its content, this will cause
* `TransitionGroup` to transition the child out and back in.
*/
children: PropTypes.node,
/**
* A convenience prop that enables or disables appear animations
* for all children. Note that specifying this will override any defaults set
* on individual children Transitions.
*/
appear: PropTypes.bool,
/**
* A convenience prop that enables or disables enter animations
* for all children. Note that specifying this will override any defaults set
* on individual children Transitions.
*/
enter: PropTypes.bool,
/**
* A convenience prop that enables or disables exit animations
* for all children. Note that specifying this will override any defaults set
* on individual children Transitions.
*/
exit: PropTypes.bool,
/**
* You may need to apply reactive updates to a child as it is exiting.
* This is generally done by using `cloneElement` however in the case of an exiting
* child the element has already been removed and not accessible to the consumer.
*
* If you do need to update a child as it leaves you can provide a `childFactory`
* to wrap every child, even the ones that are leaving.
*
* @type Function(child: ReactElement) -> ReactElement
*/
childFactory: PropTypes.func,
};
TransitionGroup.defaultProps = defaultProps;
export default TransitionGroup;
================================================
FILE: src/TransitionGroupContext.js
================================================
import React from 'react';
export default React.createContext(null);
================================================
FILE: src/config.js
================================================
export default {
disabled: false,
};
================================================
FILE: src/index.js
================================================
export { default as CSSTransition } from './CSSTransition';
export { default as ReplaceTransition } from './ReplaceTransition';
export { default as SwitchTransition } from './SwitchTransition';
export { default as TransitionGroup } from './TransitionGroup';
export { default as Transition } from './Transition';
export { default as config } from './config';
================================================
FILE: src/utils/ChildMapping.js
================================================
import { Children, cloneElement, isValidElement } from 'react';
/**
* Given `this.props.children`, return an object mapping key to child.
*
* @param {*} children `this.props.children`
* @return {object} Mapping of key to child
*/
export function getChildMapping(children, mapFn) {
let mapper = (child) =>
mapFn && isValidElement(child) ? mapFn(child) : child;
let result = Object.create(null);
if (children)
Children.map(children, (c) => c).forEach((child) => {
// run the map function here instead so that the key is the computed one
result[child.key] = mapper(child);
});
return result;
}
/**
* When you're adding or removing children some may be added or removed in the
* same render pass. We want to show *both* since we want to simultaneously
* animate elements in and out. This function takes a previous set of keys
* and a new set of keys and merges them with its best guess of the correct
* ordering. In the future we may expose some of the utilities in
* ReactMultiChild to make this easy, but for now React itself does not
* directly have this concept of the union of prevChildren and nextChildren
* so we implement it here.
*
* @param {object} prev prev children as returned from
* `ReactTransitionChildMapping.getChildMapping()`.
* @param {object} next next children as returned from
* `ReactTransitionChildMapping.getChildMapping()`.
* @return {object} a key set that contains all keys in `prev` and all keys
* in `next` in a reasonable order.
*/
export function mergeChildMappings(prev, next) {
prev = prev || {};
next = next || {};
function getValueForKey(key) {
return key in next ? next[key] : prev[key];
}
// For each key of `next`, the list of keys to insert before that key in
// the combined list
let nextKeysPending = Object.create(null);
let pendingKeys = [];
for (let prevKey in prev) {
if (prevKey in next) {
if (pendingKeys.length) {
nextKeysPending[prevKey] = pendingKeys;
pendingKeys = [];
}
} else {
pendingKeys.push(prevKey);
}
}
let i;
let childMapping = {};
for (let nextKey in next) {
if (nextKeysPending[nextKey]) {
for (i = 0; i < nextKeysPending[nextKey].length; i++) {
let pendingNextKey = nextKeysPending[nextKey][i];
childMapping[nextKeysPending[nextKey][i]] =
getValueForKey(pendingNextKey);
}
}
childMapping[nextKey] = getValueForKey(nextKey);
}
// Finally, add the keys which didn't appear before any key in `next`
for (i = 0; i < pendingKeys.length; i++) {
childMapping[pendingKeys[i]] = getValueForKey(pendingKeys[i]);
}
return childMapping;
}
function getProp(child, prop, props) {
return props[prop] != null ? props[prop] : child.props[prop];
}
export function getInitialChildMapping(props, onExited) {
return getChildMapping(props.children, (child) => {
return cloneElement(child, {
onExited: onExited.bind(null, child),
in: true,
appear: getProp(child, 'appear', props),
enter: getProp(child, 'enter', props),
exit: getProp(child, 'exit', props),
});
});
}
export function getNextChildMapping(nextProps, prevChildMapping, onExited) {
let nextChildMapping = getChildMapping(nextProps.children);
let children = mergeChildMappings(prevChildMapping, nextChildMapping);
Object.keys(children).forEach((key) => {
let child = children[key];
if (!isValidElement(child)) return;
const hasPrev = key in prevChildMapping;
const hasNext = key in nextChildMapping;
const prevChild = prevChildMapping[key];
const isLeaving = isValidElement(prevChild) && !prevChild.props.in;
// item is new (entering)
if (hasNext && (!hasPrev || isLeaving)) {
// console.log('entering', key)
children[key] = cloneElement(child, {
onExited: onExited.bind(null, child),
in: true,
exit: getProp(child, 'exit', nextProps),
enter: getProp(child, 'enter', nextProps),
});
} else if (!hasNext && hasPrev && !isLeaving) {
// item is old (exiting)
// console.log('leaving', key)
children[key] = cloneElement(child, { in: false });
} else if (hasNext && hasPrev && isValidElement(prevChild)) {
// item hasn't changed transition states
// copy over the last transition props;
// console.log('unchanged', key)
children[key] = cloneElement(child, {
onExited: onExited.bind(null, child),
in: prevChild.props.in,
exit: getProp(child, 'exit', nextProps),
enter: getProp(child, 'enter', nextProps),
});
}
});
return children;
}
================================================
FILE: src/utils/PropTypes.js
================================================
import PropTypes from 'prop-types';
export const timeoutsShape =
process.env.NODE_ENV !== 'production'
? PropTypes.oneOfType([
PropTypes.number,
PropTypes.shape({
enter: PropTypes.number,
exit: PropTypes.number,
appear: PropTypes.number,
}).isRequired,
])
: null;
export const classNamesShape =
process.env.NODE_ENV !== 'production'
? PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
enter: PropTypes.string,
exit: PropTypes.string,
active: PropTypes.string,
}),
PropTypes.shape({
enter: PropTypes.string,
enterDone: PropTypes.string,
enterActive: PropTypes.string,
exit: PropTypes.string,
exitDone: PropTypes.string,
exitActive: PropTypes.string,
}),
])
: null;
================================================
FILE: src/utils/SimpleSet.js
================================================
export default class SimpleSet {
constructor() {
this.v = [];
}
clear() {
this.v.length = 0;
}
has(k) {
return this.v.indexOf(k) !== -1;
}
add(k) {
if (this.has(k)) return;
this.v.push(k);
}
delete(k) {
const idx = this.v.indexOf(k);
if (idx === -1) return false;
this.v.splice(idx, 1);
return true;
}
}
================================================
FILE: src/utils/reflow.js
================================================
export const forceReflow = (node) => node.scrollTop;
================================================
FILE: stories/.eslintrc.yml
================================================
rules:
react/prop-types: off
no-unused-vars:
- error
- varsIgnorePattern: ^_$
import/no-extraneous-dependencies:
- error
- devDependencies: true
================================================
FILE: stories/CSSTransition.js
================================================
import React, { useState } from 'react';
import { storiesOf } from '@storybook/react';
import StoryFixture from './StoryFixture';
import Fade from './transitions/CSSFade';
function ToggleFixture({ defaultIn, description, children }) {
const [show, setShow] = useState(defaultIn || false);
return (
<StoryFixture description={description}>
<div style={{ marginBottom: 10 }}>
<button
onClick={() => {
setShow(!show);
}}
>
Toggle
</button>
</div>
{React.cloneElement(children, { in: show })}
</StoryFixture>
);
}
storiesOf('CSSTransition', module)
.add('Fade', () => (
<ToggleFixture>
<Fade>asaghasg asgasg</Fade>
</ToggleFixture>
))
.add('Fade with appear', () => (
<ToggleFixture defaultIn>
<Fade appear>asaghasg asgasg</Fade>
</ToggleFixture>
))
.add('Fade with mountOnEnter', () => {
return (
<ToggleFixture>
<Fade mountOnEnter>Fade with mountOnEnter</Fade>
</ToggleFixture>
);
})
.add('Fade with unmountOnExit', () => {
return (
<ToggleFixture>
<Fade unmountOnExit>Fade with unmountOnExit</Fade>
</ToggleFixture>
);
});
================================================
FILE: stories/CSSTransitionGroupFixture.js
================================================
import React from 'react';
import TransitionGroup from '../src/TransitionGroup';
import StoryFixture from './StoryFixture';
class CSSTransitionGroupFixture extends React.Component {
static defaultProps = {
items: [],
};
count = this.props.items.length;
state = {
items: this.props.items,
};
handleAddItem = () => {
this.setState(({ items }) => ({
items: [...items, `Item number: ${++this.count}`],
}));
};
handleRemoveItems = () => {
this.setState(({ items }) => {
items = items.slice();
items.splice(1, 3);
return { items };
});
};
handleRemoveItem = (item) => {
this.setState(({ items }) => ({
items: items.filter((i) => i !== item),
}));
};
render() {
const { items: _, description, children, ...rest } = this.props;
// e.g. `Fade`, see where `CSSTransitionGroupFixture` is used
const { type: TransitionType, props: transitionTypeProps } =
React.Children.only(children);
return (
<StoryFixture description={description}>
<div style={{ marginBottom: 10 }}>
<button onClick={this.handleAddItem}>Add Item</button>{' '}
<button onClick={this.handleRemoveItems}>Remove a few</button>
</div>
<TransitionGroup component="div" {...rest}>
{this.state.items.map((item) => (
<TransitionType {...transitionTypeProps} key={item}>
{item}
<button onClick={() => this.handleRemoveItem(item)}>
×
</button>
</TransitionType>
))}
</TransitionGroup>
</StoryFixture>
);
}
}
export default CSSTransitionGroupFixture;
================================================
FILE: stories/NestedTransition.js
================================================
import React, { useState } from 'react';
import StoryFixture from './StoryFixture';
import Fade from './transitions/CSSFadeForTransitionGroup';
import Scale from './transitions/Scale';
function FadeAndScale(props) {
return (
<Fade {...props}>
<div>I will fade</div>
{/*
We also want to scale in at the same time so we pass the `in` state here as well, so it enters
at the same time as the Fade.
Note also the `appear` since the Fade will happen when the item mounts, the Scale transition
will mount at the same time as the div we want to scale, so we need to tell it to animate as
it _appears_.
*/}
<Scale in={props.in} appear>
I should scale
</Scale>
</Fade>
);
}
function Example() {
const [showNested, setShowNested] = useState(false);
return (
<StoryFixture description="nested Transitions">
<h3>Nested Animations</h3>
<button
onClick={() => {
setShowNested(!showNested);
}}
>
Click to see 2 nested animations
</button>
<FadeAndScale in={showNested} mountOnEnter unmountOnExit />
</StoryFixture>
);
}
export default Example;
================================================
FILE: stories/ReplaceTransition.js
================================================
import { css } from 'astroturf';
import React, { useState } from 'react';
import { storiesOf } from '@storybook/react';
import ReplaceTransition from '../src/ReplaceTransition';
import CSSTransition from '../src/CSSTransition';
const FADE_TIMEOUT = 1000;
const styles = css`
.enter {
opacity: 0.01;
}
.enter.enter-active {
position: absolute;
left: 0;
right: 0;
opacity: 1;
transition: opacity ${FADE_TIMEOUT * 2}ms ease-in;
transition-delay: ${FADE_TIMEOUT}ms;
}
.exit {
opacity: 1;
}
.exit.exit-active {
opacity: 0.01;
transition: opacity ${FADE_TIMEOUT}ms ease-in;
}
.box {
padding: 20px;
background-color: #ccc;
}
.container {
position: relative;
}
`;
const defaultProps = {
in: false,
timeout: FADE_TIMEOUT * 2,
};
function Fade(props) {
return (
<CSSTransition {...props} className={styles.box} classNames={styles} />
);
}
Fade.defaultProps = defaultProps;
function Example({ children }) {
const [show, setShow] = useState(false);
return (
<div>
<button
onClick={() => {
setShow(!show);
}}
>
toggle (in: "{String(show)}")
</button>
{React.cloneElement(children, { in: show })}
</div>
);
}
storiesOf('Replace Transition', module).add('Animates on all', () => {
const firstNodeRef = React.createRef();
const secondNodeRef = React.createRef();
return (
<Example>
<ReplaceTransition
in={false} // `Example` is overriding this prop
className={styles.container}
onEnter={() => console.log('onEnter')}
onEntering={() => console.log('onEntering')}
onEntered={() => console.log('onEntered')}
onExit={() => console.log('onExit')}
onExiting={() => console.log('onExiting')}
onExited={() => console.log('onExited')}
>
<Fade nodeRef={firstNodeRef}>
<div ref={firstNodeRef}>in True</div>
</Fade>
<Fade nodeRef={secondNodeRef}>
<div ref={secondNodeRef}>in False</div>
</Fade>
</ReplaceTransition>
</Example>
);
});
================================================
FILE: stories/StoryFixture.js
================================================
import PropTypes from 'prop-types';
import React from 'react';
const propTypes = {
description: PropTypes.string,
};
function StoryFixture({ description, children }) {
return (
<div>
<p>{description}</p>
{children}
</div>
);
}
StoryFixture.propTypes = propTypes;
export default StoryFixture;
================================================
FILE: stories/Transition.js
================================================
import React, { useState } from 'react';
import { storiesOf } from '@storybook/react';
import StoryFixture from './StoryFixture';
import {
Fade,
Collapse,
FadeForwardRef,
FadeInnerRef,
} from './transitions/Bootstrap';
function ToggleFixture({ defaultIn, description, children }) {
const [show, setShow] = useState(defaultIn);
return (
<StoryFixture description={description}>
<div style={{ marginBottom: 10 }}>
<button
onClick={() => {
setShow(!show);
}}
>
Toggle
</button>
</div>
{React.cloneElement(children, { in: show })}
</StoryFixture>
);
}
storiesOf('Transition', module)
.add('Bootstrap Fade', () => (
<ToggleFixture>
<Fade>asaghasg asgasg</Fade>
</ToggleFixture>
))
.add('Bootstrap Collapse', () => (
<ToggleFixture>
<Collapse>
asaghasg asgasg
<div>foo</div>
<div>bar</div>
</Collapse>
</ToggleFixture>
))
.add('Fade using React.forwardRef', () => {
const nodeRef = React.createRef();
return (
<ToggleFixture>
<FadeForwardRef ref={nodeRef}>
Fade using React.forwardRef
</FadeForwardRef>
</ToggleFixture>
);
})
.add('Fade using innerRef', () => {
const nodeRef = React.createRef();
return (
<ToggleFixture>
<FadeInnerRef innerRef={nodeRef}>Fade using innerRef</FadeInnerRef>
</ToggleFixture>
);
})
.add('Fade with mountOnEnter', () => {
return (
<ToggleFixture>
<Fade mountOnEnter>Fade with mountOnEnter</Fade>
</ToggleFixture>
);
})
.add('Fade with unmountOnExit', () => {
return (
<ToggleFixture>
<Fade unmountOnExit>Fade with unmountOnExit</Fade>
</ToggleFixture>
);
});
================================================
FILE: stories/TransitionGroup.js
================================================
import React, { useEffect, useState } from 'react';
import { storiesOf } from '@storybook/react';
import TransitionGroup from '../src/TransitionGroup';
import CSSTransitionGroupFixture from './CSSTransitionGroupFixture';
import NestedTransition from './NestedTransition';
import StoryFixture from './StoryFixture';
import Fade, { FADE_TIMEOUT } from './transitions/CSSFadeForTransitionGroup';
storiesOf('Css Transition Group', module)
.add('Animates on all', () => (
<CSSTransitionGroupFixture
description={`
Should animate when items are added to the list but not when they are
removed or on initial appear
`}
appear
items={['Item number: 1']}
>
<Fade />
</CSSTransitionGroupFixture>
))
.add('Animates on enter', () => (
<CSSTransitionGroupFixture
description={`
Should animate when items are added to the list but not when they are
removed or on initial appear
`}
exit={false}
timeout={{ enter: FADE_TIMEOUT }}
items={['Item number: 1']}
>
<Fade />
</CSSTransitionGroupFixture>
))
.add('Animates on exit', () => (
<CSSTransitionGroupFixture
description={`
Should animate when items are removed to the list but not when they are
added or on initial appear
`}
items={['Item number: 1', 'Item number: 2', 'Item number: 3']}
>
<Fade enter={false} timeout={{ exit: FADE_TIMEOUT }} />
</CSSTransitionGroupFixture>
))
.add('Animates on appear', () => (
<CSSTransitionGroupFixture
description={`
Should animate when items first mount but not when added or removed
`}
appear
items={['Item number: 1', 'Item number: 2', 'Item number: 3']}
>
<Fade exit={false} enter={false} />
</CSSTransitionGroupFixture>
))
.add('Dynamic props', () => (
<StoryFixture
description={`
Updates to children should not break animations
`}
>
<DynamicTransition />
</StoryFixture>
))
.add('Re-entering while leaving', () => (
<StoryFixture
description={`
Should animate on enter even while exiting
`}
>
<ReEnterTransition />
</StoryFixture>
))
.add('Nested Transitions', () => <NestedTransition />);
class DynamicTransition extends React.Component {
state = { count: 0 };
handleClick = () => {
this.setState({ hide: !this.state.hide });
};
componentDidMount() {
this.interval = setInterval(() => {
this.setState({ count: this.state.count + 1 });
}, 700);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { hide, count } = this.state;
return (
<div>
<button onClick={this.handleClick}>Toggle item</button>
<TransitionGroup timeout={FADE_TIMEOUT}>
{!hide && <Fade key="item">Changing! {count}</Fade>}
</TransitionGroup>
</div>
);
}
}
function ReEnterTransition() {
const [hide, setHide] = useState(false);
useEffect(() => {
if (hide) {
setTimeout(() => {
console.log('re-entering!');
setHide(false);
}, 0.5 * FADE_TIMEOUT);
}
}, [hide]);
return (
<div>
<button
onClick={() => {
setHide(true);
}}
>
Remove and re-add
</button>
<TransitionGroup timeout={FADE_TIMEOUT}>
{!hide && <Fade key="item">I'm entering!</Fade>}
</TransitionGroup>
</div>
);
}
================================================
FILE: stories/index.js
================================================
import './Transition';
import './CSSTransition';
import './TransitionGroup';
import './ReplaceTransition';
================================================
FILE: stories/transitions/Bootstrap.js
================================================
import { css } from 'astroturf';
import React, { useEffect, useRef } from 'react';
import style from 'dom-helpers/css';
import Transition, {
EXITED,
ENTERED,
ENTERING,
EXITING,
} from '../../src/Transition';
const styles = css`
.fade {
opacity: 0;
transition: opacity 0.5s linear;
}
.fade.in {
opacity: 1;
}
.collapse {
display: none;
}
.collapse.in {
display: block;
}
.collapsing {
position: relative;
height: 0;
overflow: hidden;
transition: 0.35s ease;
transition-property: height, visibility;
}
`;
const fadeStyles = {
[ENTERING]: styles.in,
[ENTERED]: styles.in,
};
export function Fade(props) {
const nodeRef = useRef();
return (
<Transition
{...props}
nodeRef={nodeRef}
className={styles.fade}
timeout={500}
>
{(status) => (
<div
ref={nodeRef}
className={`${styles.fade} ${fadeStyles[status] || ''}`}
>
{props.children}
</div>
)}
</Transition>
);
}
function getHeight(elem) {
let value = elem.offsetHeight;
let margins = ['marginTop', 'marginBottom'];
return (
value +
parseInt(style(elem, margins[0]), 10) +
parseInt(style(elem, margins[1]), 10)
);
}
const collapseStyles = {
[EXITED]: styles.collapse,
[EXITING]: styles.collapsing,
[ENTERING]: styles.collapsing,
[ENTERED]: `${styles.collapse} ${styles.in}`,
};
export class Collapse extends React.Component {
nodeRef = React.createRef();
/* -- Expanding -- */
handleEnter = () => {
this.nodeRef.current.style.height = '0';
};
handleEntering = () => {
this.nodeRef.current.style.height = `${this.nodeRef.current.scrollHeight}px`;
};
handleEntered = () => {
this.nodeRef.current.style.height = null;
};
/* -- Collapsing -- */
handleExit = () => {
this.nodeRef.current.style.height = getHeight(this.nodeRef.current) + 'px';
this.nodeRef.current.offsetHeight; // eslint-disable-line no-unused-expressions
};
handleExiting = () => {
this.nodeRef.current.style.height = '0';
};
render() {
const { children, ...rest } = this.props;
return (
<Transition
{...rest}
nodeRef={this.nodeRef}
timeout={350}
onEnter={this.handleEnter}
onEntering={this.handleEntering}
onEntered={this.handleEntered}
onExit={this.handleExit}
onExiting={this.handleExiting}
>
{(state, props) => (
<div ref={this.nodeRef} className={collapseStyles[state]} {...props}>
{children}
</div>
)}
</Transition>
);
}
}
export function FadeInnerRef(props) {
const nodeRef = useMergedRef(props.innerRef);
return (
<Transition
{...props}
nodeRef={nodeRef}
className={styles.fade}
timeout={150}
>
{(status) => (
<div
ref={nodeRef}
className={`${styles.fade} ${fadeStyles[status] || ''}`}
>
{props.children}
</div>
)}
</Transition>
);
}
export const FadeForwardRef = React.forwardRef((props, ref) => {
return <FadeInnerRef {...props} innerRef={ref} />;
});
/**
* Compose multiple refs, there may be different implementations
* This one is derived from
* e.g. https://github.com/react-restart/hooks/blob/ed37bf3dfc8fc1d9234a6d8fe0af94d69fad3b74/src/useMergedRefs.ts
* Also here are good discussion about this
* https://github.com/facebook/react/issues/13029
* @param ref
* @returns {React.MutableRefObject<undefined>}
*/
function useMergedRef(ref) {
const nodeRef = React.useRef();
useEffect(function () {
if (ref) {
if (typeof ref === 'function') {
ref(nodeRef.current);
} else {
ref.current = nodeRef.current;
}
}
});
return nodeRef;
}
================================================
FILE: stories/transitions/CSSFade.js
================================================
import { css } from 'astroturf';
import React, { useRef } from 'react';
import CSSTransition from '../../src/CSSTransition';
export const FADE_TIMEOUT = 1000;
const styles = css`
.default {
opacity: 0;
}
.enter-done {
opacity: 1;
}
.enter,
.appear {
opacity: 0.01;
}
.enter.enter-active,
.appear.appear-active {
opacity: 1;
transition: opacity ${FADE_TIMEOUT}ms ease-in;
}
.exit {
opacity: 1;
}
.exit.exit-active {
opacity: 0.01;
transition: opacity ${0.8 * FADE_TIMEOUT}ms ease-in;
}
`;
const defaultProps = {
in: false,
timeout: FADE_TIMEOUT,
};
function Fade(props) {
const nodeRef = useRef();
return (
<CSSTransition {...props} classNames={styles} nodeRef={nodeRef}>
<div ref={nodeRef} className={styles.default}>
{props.children}
</div>
</CSSTransition>
);
}
Fade.defaultProps = defaultProps;
export default Fade;
================================================
FILE: stories/transitions/CSSFadeForTransitionGroup.js
================================================
import { css } from 'astroturf';
import React, { useRef } from 'react';
import CSSTransition from '../../src/CSSTransition';
export const FADE_TIMEOUT = 1000;
const styles = css`
.enter,
.appear {
opacity: 0.01;
}
.enter.enter-active,
.appear.appear-active {
opacity: 1;
transition: opacity ${FADE_TIMEOUT}ms ease-in;
}
.exit {
opacity: 1;
}
.exit.exit-active {
opacity: 0.01;
transition: opacity ${0.8 * FADE_TIMEOUT}ms ease-in;
}
`;
const defaultProps = {
in: false,
timeout: FADE_TIMEOUT,
};
function Fade(props) {
const nodeRef = useRef();
return (
<CSSTransition {...props} classNames={styles} nodeRef={nodeRef}>
<div ref={nodeRef}>{props.children}</div>
</CSSTransition>
);
}
Fade.defaultProps = defaultProps;
export default Fade;
================================================
FILE: stories/transitions/Scale.js
================================================
import { css } from 'astroturf';
import React, { useRef } from 'react';
import CSSTransition from '../../src/CSSTransition';
export const SCALE_TIMEOUT = 1000;
const styles = css`
.enter,
.appear {
transform: scale(0);
}
.enter.enter-active,
.appear.appear-active {
transform: scale(1);
transition: transform ${SCALE_TIMEOUT}ms;
}
.exit {
transform: scale(1);
}
.exit.exit-active {
transform: scale(0);
transition: transform ${SCALE_TIMEOUT}ms;
}
`;
const defaultProps = {
in: false,
timeout: SCALE_TIMEOUT,
};
function Scale(props) {
const nodeRef = useRef();
return (
<CSSTransition {...props} classNames={styles} nodeRef={nodeRef}>
<div ref={nodeRef}>{props.children}</div>
</CSSTransition>
);
}
Scale.defaultProps = defaultProps;
export default Scale;
================================================
FILE: test/.eslintrc.yml
================================================
env:
jest: true
es6: true
rules:
no-require: off
global-require: off
no-console: off
react/no-multi-comp: off
react/no-render-return-value: off
react/no-find-dom-node: off
react/prop-types: off
react/prefer-stateless-function: off
react/jsx-boolean-value: off
react/no-string-refs: off
import/no-extraneous-dependencies:
- error
- devDependencies: true
================================================
FILE: test/CSSTransition-test.js
================================================
import React from 'react';
import { render, waitFor } from './utils';
import CSSTransition from '../src/CSSTransition';
import TransitionGroup from '../src/TransitionGroup';
describe('CSSTransition', () => {
it('should flush new props to the DOM before initiating a transition', (done) => {
const nodeRef = React.createRef();
const { setProps } = render(
<CSSTransition
in={false}
nodeRef={nodeRef}
timeout={0}
classNames="test"
onEnter={() => {
expect(nodeRef.current.classList.contains('test-class')).toEqual(
true
);
expect(nodeRef.current.classList.contains('test-entering')).toEqual(
false
);
done();
}}
>
<div ref={nodeRef} />
</CSSTransition>
);
expect(nodeRef.current.classList.contains('test-class')).toEqual(false);
setProps({
in: true,
className: 'test-class',
});
});
describe('entering', () => {
it('should apply classes at each transition state', async () => {
let count = 0;
let done = false;
const nodeRef = React.createRef();
const { setProps } = render(
<CSSTransition nodeRef={nodeRef} timeout={10} classNames="test">
<div ref={nodeRef} />
</CSSTransition>
);
setProps({
in: true,
onEnter() {
count++;
expect(nodeRef.current.className).toEqual('test-enter');
},
onEntering() {
count++;
expect(nodeRef.current.className).toEqual(
'test-enter test-enter-active'
);
},
onEntered() {
expect(nodeRef.current.className).toEqual('test-enter-done');
expect(count).toEqual(2);
done = true;
},
});
await waitFor(() => {
expect(done).toBe(true);
});
});
it('should apply custom classNames names', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
<CSSTransition
timeout={10}
nodeRef={nodeRef}
classNames={{
enter: 'custom',
enterActive: 'custom-super-active',
enterDone: 'custom-super-done',
}}
>
<div ref={nodeRef} />
</CSSTransition>
);
setProps({
in: true,
onEnter() {
count++;
expect(nodeRef.current.className).toEqual('custom');
},
onEntering() {
count++;
expect(nodeRef.current.className).toEqual(
'custom custom-super-active'
);
},
onEntered() {
expect(nodeRef.current.className).toEqual('custom-super-done');
},
});
await waitFor(() => {
expect(count).toEqual(2);
});
});
});
describe('appearing', () => {
it('should apply appear classes at each transition state', async () => {
let count = 0;
const nodeRef = React.createRef();
render(
<CSSTransition
timeout={10}
nodeRef={nodeRef}
classNames="appear-test"
in={true}
appear={true}
onEnter={(isAppearing) => {
count++;
expect(isAppearing).toEqual(true);
expect(nodeRef.current.className).toEqual('appear-test-appear');
}}
onEntering={(isAppearing) => {
count++;
expect(isAppearing).toEqual(true);
expect(nodeRef.current.className).toEqual(
'appear-test-appear appear-test-appear-active'
);
}}
onEntered={(isAppearing) => {
expect(isAppearing).toEqual(true);
expect(nodeRef.current.className).toEqual(
'appear-test-appear-done appear-test-enter-done'
);
}}
>
<div ref={nodeRef} />
</CSSTransition>
);
await waitFor(() => {
expect(count).toEqual(2);
});
});
it('should lose the "*-appear-done" class after leaving and entering again', async () => {
const nodeRef = React.createRef();
let entered = false;
let exited = false;
const { setProps } = render(
<CSSTransition
timeout={10}
nodeRef={nodeRef}
classNames="appear-test"
in={true}
appear={true}
onEntered={() => {
entered = true;
}}
>
<div ref={nodeRef} />
</CSSTransition>
);
await waitFor(() => {
expect(entered).toEqual(true);
});
setProps({
in: false,
onEntered: () => {},
onExited: () => {
exited = true;
},
});
await waitFor(() => {
expect(exited).toEqual(true);
});
expect(nodeRef.current.className).toBe('appear-test-exit-done');
entered = false;
setProps({
in: true,
onEntered: () => {
entered = true;
},
});
await waitFor(() => {
expect(entered).toEqual(true);
});
expect(nodeRef.current.className).toBe('appear-test-enter-done');
});
it('should not add undefined when appearDone is not defined', async () => {
const nodeRef = React.createRef();
let done = false;
render(
<CSSTransition
timeout={10}
nodeRef={nodeRef}
classNames={{ appear: 'appear-test' }}
in={true}
appear={true}
onEnter={(isAppearing) => {
expect(isAppearing).toEqual(true);
expect(nodeRef.current.className).toEqual('appear-test');
}}
onEntered={(isAppearing) => {
expect(isAppearing).toEqual(true);
expect(nodeRef.current.className).toEqual('');
done = true;
}}
>
<div ref={nodeRef} />
</CSSTransition>
);
await waitFor(() => {
expect(done).toEqual(true);
});
});
it('should not be appearing in normal enter mode', async () => {
let count = 0;
const nodeRef = React.createRef();
render(
<CSSTransition
timeout={10}
nodeRef={nodeRef}
classNames="not-appear-test"
appear={true}
>
<div ref={nodeRef} />
</CSSTransition>
).setProps({
in: true,
onEnter(isAppearing) {
count++;
expect(isAppearing).toEqual(false);
expect(nodeRef.current.className).toEqual('not-appear-test-enter');
},
onEntering(isAppearing) {
count++;
expect(isAppearing).toEqual(false);
expect(nodeRef.current.className).toEqual(
'not-appear-test-enter not-appear-test-enter-active'
);
},
onEntered(isAppearing) {
expect(isAppearing).toEqual(false);
expect(nodeRef.current.className).toEqual(
'not-appear-test-enter-done'
);
},
});
await waitFor(() => {
expect(count).toEqual(2);
});
});
it('should not enter the transition states when appear=false', () => {
const nodeRef = React.createRef();
render(
<CSSTransition
timeout={10}
nodeRef={nodeRef}
classNames="appear-fail-test"
in={true}
appear={false}
onEnter={() => {
throw Error('Enter called!');
}}
onEntering={() => {
throw Error('Entring called!');
}}
onEntered={() => {
throw Error('Entred called!');
}}
>
<div ref={nodeRef} />
</CSSTransition>
);
});
});
describe('exiting', () => {
it('should apply classes at each transition state', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
<CSSTransition in nodeRef={nodeRef} timeout={10} classNames="test">
<div ref={nodeRef} />
</CSSTransition>
);
setProps({
in: false,
onExit() {
count++;
expect(nodeRef.current.className).toEqual('test-exit');
},
onExiting() {
count++;
expect(nodeRef.current.className).toEqual(
'test-exit test-exit-active'
);
},
onExited() {
expect(nodeRef.current.className).toEqual('test-exit-done');
},
});
await waitFor(() => {
expect(count).toEqual(2);
});
});
it('should apply custom classNames names', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
<CSSTransition
in
nodeRef={nodeRef}
timeout={10}
classNames={{
exit: 'custom',
exitActive: 'custom-super-active',
exitDone: 'custom-super-done',
}}
>
<div ref={nodeRef} />
</CSSTransition>
);
setProps({
in: false,
onExit() {
count++;
expect(nodeRef.current.className).toEqual('custom');
},
onExiting() {
count++;
expect(nodeRef.current.className).toEqual(
'custom custom-super-active'
);
},
onExited() {
expect(nodeRef.current.className).toEqual('custom-super-done');
},
});
await waitFor(() => {
expect(count).toEqual(2);
});
});
it('should support empty prefix', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
<CSSTransition in nodeRef={nodeRef} timeout={10}>
<div ref={nodeRef} />
</CSSTransition>
);
setProps({
in: false,
onExit() {
count++;
expect(nodeRef.current.className).toEqual('exit');
},
onExiting() {
count++;
expect(nodeRef.current.className).toEqual('exit exit-active');
},
onExited() {
expect(nodeRef.current.className).toEqual('exit-done');
},
});
await waitFor(() => {
expect(count).toEqual(2);
});
});
});
describe('reentering', () => {
it('should remove dynamically applied classes', async () => {
let count = 0;
class Test extends React.Component {
render() {
const { direction, text, nodeRef, ...props } = this.props;
return (
<TransitionGroup
component={null}
childFactory={(child) =>
React.cloneElement(child, {
classNames: direction,
})
}
>
<CSSTransition
key={text}
timeout={100}
nodeRef={nodeRef}
{...props}
>
<span ref={nodeRef}>{text}</span>
</CSSTransition>
</TransitionGroup>
);
}
}
const nodeRef = {
foo: React.createRef(),
bar: React.createRef(),
};
const { setProps } = render(
<Test direction="down" text="foo" nodeRef={nodeRef.foo} />
);
setProps({
direction: 'up',
text: 'bar',
nodeRef: nodeRef.bar,
onEnter() {
count++;
expect(nodeRef.bar.current.className).toEqual('up-enter');
},
onEntering() {
count++;
expect(nodeRef.bar.current.className).toEqual(
'up-enter up-enter-active'
);
},
});
await waitFor(() => {
expect(count).toEqual(2);
});
setProps({
direction: 'down',
text: 'foo',
nodeRef: nodeRef.foo,
onEntering() {
count++;
expect(nodeRef.foo.current.className).toEqual(
'down-enter down-enter-active'
);
},
onEntered() {
count++;
expect(nodeRef.foo.current.className).toEqual('down-enter-done');
},
});
await waitFor(() => {
expect(count).toEqual(4);
});
});
});
});
================================================
FILE: test/CSSTransitionGroup-test.js
================================================
import hasClass from 'dom-helpers/hasClass';
import CSSTransition from '../src/CSSTransition';
let React;
let ReactDOM;
let TransitionGroup;
let act;
let render;
// Most of the real functionality is covered in other unit tests, this just
// makes sure we're wired up correctly.
describe('CSSTransitionGroup', () => {
let container;
let consoleErrorSpy;
function YoloTransition({ id, ...props }) {
const nodeRef = React.useRef();
return (
<CSSTransition nodeRef={nodeRef} classNames="yolo" timeout={0} {...props}>
<span ref={nodeRef} id={id} />
</CSSTransition>
);
}
beforeEach(() => {
jest.resetModuleRegistry();
jest.useFakeTimers();
React = require('react');
ReactDOM = require('react-dom');
const testUtils = require('./utils');
act = testUtils.act;
const baseRender = testUtils.render;
render = (element, container) =>
baseRender(<React.StrictMode>{element}</React.StrictMode>, { container });
TransitionGroup = require('../src/TransitionGroup');
container = document.createElement('div');
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
consoleErrorSpy.mockRestore();
jest.useRealTimers();
});
it('should clean-up silently after the timeout elapses', () => {
render(
<TransitionGroup enter={false}>
<YoloTransition key="one" id="one" />
</TransitionGroup>,
container
);
const transitionGroupDiv = container.childNodes[0];
expect(transitionGroupDiv.childNodes.length).toBe(1);
render(
<TransitionGroup enter={false}>
<YoloTransition key="two" id="two" />
</TransitionGroup>,
container
);
expect(transitionGroupDiv.childNodes.length).toBe(2);
expect(transitionGroupDiv.childNodes[0].id).toBe('two');
expect(transitionGroupDiv.childNodes[1].id).toBe('one');
act(() => {
jest.runAllTimers();
});
// No warnings
expect(consoleErrorSpy).not.toHaveBeenCalled();
// The leaving child has been removed
expect(transitionGroupDiv.childNodes.length).toBe(1);
expect(transitionGroupDiv.childNodes[0].id).toBe('two');
});
it('should keep both sets of DOM nodes around', () => {
render(
<TransitionGroup>
<YoloTransition key="one" id="one" />
</TransitionGroup>,
container
);
const transitionGroupDiv = container.childNodes[0];
expect(transitionGroupDiv.childNodes.length).toBe(1);
render(
<TransitionGroup>
<YoloTransition key="two" id="two" />
</TransitionGroup>,
container
);
expect(transitionGroupDiv.childNodes.length).toBe(2);
expect(transitionGroupDiv.childNodes[0].id).toBe('two');
expect(transitionGroupDiv.childNodes[1].id).toBe('one');
});
it('should switch transitionLeave from false to true', () => {
render(
<TransitionGroup enter={false} leave={false}>
<YoloTransition key="one" id="one" />
</TransitionGroup>,
container
);
const transitionGroupDiv = container.childNodes[0];
expect(transitionGroupDiv.childNodes.length).toBe(1);
render(
<TransitionGroup enter={false} leave={false}>
<YoloTransition key="two" id="two" />
</TransitionGroup>,
container
);
act(() => {
jest.runAllTimers();
});
expect(transitionGroupDiv.childNodes.length).toBe(1);
render(
<TransitionGroup enter={false} leave>
<YoloTransition key="three" id="three" />
</TransitionGroup>,
container
);
expect(transitionGroupDiv.childNodes.length).toBe(2);
expect(transitionGroupDiv.childNodes[0].id).toBe('three');
expect(transitionGroupDiv.childNodes[1].id).toBe('two');
});
it('should work with a null child', () => {
render(<TransitionGroup>{[null]}</TransitionGroup>, container);
});
it('should work with a child which renders as null', () => {
const NullComponent = () => null;
// Testing the whole lifecycle of entering and exiting,
// because those lifecycle methods used to fail when the DOM node was null.
render(<TransitionGroup />, container);
render(
<TransitionGroup>
<CSSTransition classNames="yolo" timeout={0}>
<NullComponent />
</CSSTransition>
</TransitionGroup>,
container
);
render(<TransitionGroup />, container);
});
it('should transition from one to null', () => {
render(
<TransitionGroup>
<YoloTransition key="one" id="one" />
</TransitionGroup>,
container
);
const transitionGroupDiv = container.childNodes[0];
expect(transitionGroupDiv.childNodes.length).toBe(1);
render(<TransitionGroup>{null}</TransitionGroup>, container);
// (Here, we expect the original child to stick around but test that no
// exception is thrown)
expect(transitionGroupDiv.childNodes.length).toBe(1);
expect(transitionGroupDiv.childNodes[0].id).toBe('one');
});
it('should transition from false to one', () => {
render(<TransitionGroup>{false}</TransitionGroup>, container);
const transitionGroupDiv = container.childNodes[0];
expect(transitionGroupDiv.childNodes.length).toBe(0);
render(
<TransitionGroup>
<YoloTransition key="one" id="one" />
</TransitionGroup>,
container
);
expect(transitionGroupDiv.childNodes.length).toBe(1);
expect(transitionGroupDiv.childNodes[0].id).toBe('one');
});
it('should clear transition timeouts when unmounted', () => {
class Component extends React.Component {
render() {
return <TransitionGroup>{this.props.children}</TransitionGroup>;
}
}
render(<Component />, container);
render(
<Component>
<YoloTransition key="yolo" id="yolo" />
</Component>,
container
);
ReactDOM.unmountComponentAtNode(container);
// Testing that no exception is thrown here, as the timeout has been cleared.
act(() => {
jest.runAllTimers();
});
});
it('should handle unmounted elements properly', () => {
class Child extends React.Component {
render() {
if (!this.props.show) return null;
return <div />;
}
}
class Component extends React.Component {
state = { showChild: true };
componentDidMount() {
this.setState({ showChild: false });
}
render() {
return (
<TransitionGroup appear={true}>
<Child show={this.state.showChild} />
</TransitionGroup>
);
}
}
render(<Component />, container);
// Testing that no exception is thrown here, as the timeout has been cleared.
act(() => {
jest.runAllTimers();
});
});
it('should work with custom component wrapper cloning children', () => {
const extraClassNameProp = 'wrapper-item';
class Wrapper extends React.Component {
render() {
return (
<div>
{React.Children.map(this.props.children, (child) =>
React.cloneElement(child, { className: extraClassNameProp })
)}
</div>
);
}
}
class Child extends React.Component {
render() {
return <div {...this.props} />;
}
}
class Component extends React.Component {
render() {
return (
<TransitionGroup component={Wrapper}>
<Child />
</TransitionGroup>
);
}
}
render(<Component />, container);
const transitionGroupDiv = container.childNodes[0];
transitionGroupDiv.childNodes.forEach((child) => {
expect(hasClass(child, extraClassNameProp)).toBe(true);
});
// Testing that no exception is thrown here, as the timeout has been cleared.
act(() => {
jest.runAllTimers();
});
});
});
================================================
FILE: test/ChildMapping-test.js
================================================
let React;
let ChildMapping;
describe('ChildMapping', () => {
beforeEach(() => {
React = require('react');
ChildMapping = require('../src/utils/ChildMapping');
});
it('should support getChildMapping', () => {
let oneone = <div key="oneone" />;
let onetwo = <div key="onetwo" />;
let one = (
<div key="one">
{oneone}
{onetwo}
</div>
);
let two = <div key="two">foo</div>;
let component = (
<div>
{one}
{two}
</div>
);
let mapping = ChildMapping.getChildMapping(component.props.children);
expect(mapping['.$one'].props).toEqual(one.props);
expect(mapping['.$two'].props).toEqual(two.props);
});
it('should support mergeChildMappings for adding keys', () => {
let prev = {
one: true,
two: true,
};
let next = {
one: true,
two: true,
three: true,
};
expect(ChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
three: true,
});
});
it('should support mergeChildMappings for removing keys', () => {
let prev = {
one: true,
two: true,
three: true,
};
let next = {
one: true,
two: true,
};
expect(ChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
three: true,
});
});
it('should support mergeChildMappings for adding and removing', () => {
let prev = {
one: true,
two: true,
three: true,
};
let next = {
one: true,
two: true,
four: true,
};
expect(ChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
three: true,
four: true,
});
});
it('should reconcile overlapping insertions and deletions', () => {
let prev = {
one: true,
two: true,
four: true,
five: true,
};
let next = {
one: true,
two: true,
three: true,
five: true,
};
expect(ChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
three: true,
four: true,
five: true,
});
});
it('should support mergeChildMappings with undefined input', () => {
let prev = {
one: true,
two: true,
};
let next;
expect(ChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
});
prev = undefined;
next = {
three: true,
four: true,
};
expect(ChildMapping.mergeChildMappings(prev, next)).toEqual({
three: true,
four: true,
});
});
});
================================================
FILE: test/SSR-test.js
================================================
/**
* @jest-environment node
*/
// test that import does not crash
import * as ReactTransitionGroup from '../src'; // eslint-disable-line no-unused-vars
describe('SSR', () => {
it('should import react-transition-group in node env', () => {});
});
================================================
FILE: test/SwitchTransition-test.js
================================================
import React from 'react';
import { act, render } from './utils';
import Transition, { ENTERED } from '../src/Transition';
import SwitchTransition from '../src/SwitchTransition';
describe('SwitchTransition', () => {
let log, Parent;
beforeEach(() => {
log = [];
let events = {
onEnter: (m) => log.push(m ? 'appear' : 'enter'),
onEntering: (m) => log.push(m ? 'appearing' : 'entering'),
onEntered: (m) => log.push(m ? 'appeared' : 'entered'),
onExit: () => log.push('exit'),
onExiting: () => log.push('exiting'),
onExited: () => log.push('exited'),
};
const nodeRef = React.createRef();
Parent = function Parent({ on, rendered = true }) {
return (
<SwitchTransition>
{rendered ? (
<Transition
nodeRef={nodeRef}
timeout={0}
key={on ? 'first' : 'second'}
{...events}
>
<span ref={nodeRef}>{on ? 'first' : 'second'}</span>
</Transition>
) : null}
</SwitchTransition>
);
};
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('should have default status ENTERED', () => {
const nodeRef = React.createRef();
render(
<SwitchTransition>
<Transition nodeRef={nodeRef} timeout={0} key="first">
{(status) => {
return <span ref={nodeRef}>status: {status}</span>;
}}
</Transition>
</SwitchTransition>
);
expect(nodeRef.current.textContent).toBe(`status: ${ENTERED}`);
});
it('should have default mode: out-in', () => {
const firstNodeRef = React.createRef();
const secondNodeRef = React.createRef();
const { rerender } = render(
<SwitchTransition>
<Transition nodeRef={firstNodeRef} timeout={0} key="first">
{(status) => {
return <span ref={firstNodeRef}>first status: {status}</span>;
}}
</Transition>
</SwitchTransition>
);
rerender(
<SwitchTransition>
<Transition nodeRef={secondNodeRef} timeout={0} key="second">
{(status) => {
return <span ref={secondNodeRef}>second status: {status}</span>;
}}
</Transition>
</SwitchTransition>
);
expect(firstNodeRef.current.textContent).toBe('first status: exiting');
expect(secondNodeRef.current).toBe(null);
});
it('should work without childs', () => {
const nodeRef = React.createRef();
expect(() => {
render(
<SwitchTransition>
<Transition nodeRef={nodeRef} timeout={0} key="first">
<span ref={nodeRef} />
</Transition>
</SwitchTransition>
);
}).not.toThrow();
});
it('should switch between components on change state', () => {
const { container, setProps } = render(<Parent on={true} />);
expect(container.textContent).toBe('first');
setProps({ on: false });
expect(log).toEqual(['exit', 'exiting']);
act(() => {
jest.runAllTimers();
});
act(() => {
jest.runAllTimers();
});
expect(log).toEqual([
'exit',
'exiting',
'exited',
'enter',
'entering',
'entered',
]);
expect(container.textContent).toBe('second');
});
it('should switch between null and component', () => {
const { container, setProps } = render(
<Parent on={true} rendered={false} />
);
expect(container.textContent).toBe('');
jest.useFakeTimers();
setProps({ rendered: true });
act(() => {
jest.runAllTimers();
});
expect(log).toEqual(['enter', 'entering', 'entered']);
expect(container.textContent).toBe('first');
setProps({ on: false, rendered: true });
act(() => {
jest.runAllTimers();
});
act(() => {
jest.runAllTimers();
});
expect(log).toEqual([
'enter',
'entering',
'entered',
'exit',
'exiting',
'exited',
'enter',
'entering',
'entered',
]);
expect(container.textContent).toBe('second');
});
});
================================================
FILE: test/Transition-test.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import { render, waitFor } from './utils';
import Transition, {
UNMOUNTED,
EXITED,
ENTERING,
ENTERED,
EXITING,
} from '../src/Transition';
expect.extend({
toExist(received) {
const pass = received != null;
return pass
? {
message: () => `expected ${received} to be null or undefined`,
pass: true,
}
: {
message: () => `expected ${received} not to be null or undefined`,
pass: false,
};
},
});
describe('Transition', () => {
it('should not transition on mount', () => {
const nodeRef = React.createRef();
render(
<Transition
in
nodeRef={nodeRef}
timeout={0}
onEnter={() => {
throw new Error('should not Enter');
}}
>
{(status) => <div ref={nodeRef}>status: {status}</div>}
</Transition>
);
expect(nodeRef.current.textContent).toEqual(`status: ${ENTERED}`);
});
it('should transition on mount with `appear`', (done) => {
const nodeRef = React.createRef();
render(
<Transition
in
nodeRef={nodeRef}
timeout={0}
onEnter={() => {
throw Error('Animated!');
}}
>
<div ref={nodeRef} />
</Transition>
);
render(
<Transition
nodeRef={nodeRef}
in
appear
timeout={0}
onEnter={() => done()}
>
<div ref={nodeRef} />
</Transition>
);
});
it('should pass filtered props to children', () => {
class Child extends React.Component {
render() {
return (
<div ref={this.props.nodeRef}>
foo: {this.props.foo}, bar: {this.props.bar}
</div>
);
}
}
const nodeRef = React.createRef();
render(
<Transition
foo="foo"
bar="bar"
in
nodeRef={nodeRef}
mountOnEnter
unmountOnExit
appear
enter
exit
timeout={0}
addEndListener={() => {}}
onEnter={() => {}}
onEntering={() => {}}
onEntered={() => {}}
onExit={() => {}}
onExiting={() => {}}
onExited={() => {}}
>
<Child nodeRef={nodeRef} />
</Transition>
);
expect(nodeRef.current.textContent).toBe('foo: foo, bar: bar');
});
it('should allow addEndListener instead of timeouts', async () => {
let listener = jest.fn((end) => setTimeout(end, 0));
let done = false;
const nodeRef = React.createRef();
const { setProps } = render(
<Transition
nodeRef={nodeRef}
addEndListener={listener}
onEntered={() => {
expect(listener).toHaveBeenCalledTimes(1);
done = true;
}}
>
<div ref={nodeRef} />
</Transition>
);
setProps({ in: true });
await waitFor(() => {
expect(done).toEqual(true);
});
});
it('should fallback to timeouts with addEndListener', async () => {
let calledEnd = false;
let done = false;
let listener = (end) =>
setTimeout(() => {
calledEnd = true;
end();
}, 100);
const nodeRef = React.createRef();
const { setProps } = render(
<Transition
timeout={0}
nodeRef={nodeRef}
addEndListener={listener}
onEntered={() => {
expect(calledEnd).toEqual(false);
done = true;
}}
>
<div ref={nodeRef} />
</Transition>
);
setProps({ in: true });
await waitFor(() => {
expect(done).toEqual(true);
});
});
it('should mount/unmount immediately if not have enter/exit timeout', async () => {
const nodeRef = React.createRef();
let done = false;
const { setProps } = render(
<Transition nodeRef={nodeRef} in={true} timeout={{}}>
{(status) => <div ref={nodeRef}>status: {status}</div>}
</Transition>
);
expect(nodeRef.current.textContent).toEqual(`status: ${ENTERED}`);
let calledAfterTimeout = false;
setTimeout(() => {
calledAfterTimeout = true;
}, 10);
setProps({
in: false,
onExited() {
expect(nodeRef.current.textContent).toEqual(`status: ${EXITED}`);
if (calledAfterTimeout) {
throw new Error('wrong timeout');
}
done = true;
},
});
await waitFor(() => {
expect(done).toEqual(true);
});
});
it('should use `React.findDOMNode` when `nodeRef` is not provided', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const findDOMNodeSpy = jest.spyOn(ReactDOM, 'findDOMNode');
render(
<Transition in appear timeout={0}>
<div />
</Transition>
);
expect(findDOMNodeSpy).toHaveBeenCalled();
findDOMNodeSpy.mockRestore();
consoleSpy.mockRestore();
});
it('should not use `React.findDOMNode` when `nodeRef` is provided', () => {
const findDOMNodeSpy = jest.spyOn(ReactDOM, 'findDOMNode');
const nodeRef = React.createRef();
render(
<Transition nodeRef={nodeRef} in appear timeout={0}>
<div ref={nodeRef} />
</Transition>
);
expect(findDOMNodeSpy).not.toHaveBeenCalled();
findDOMNodeSpy.mockRestore();
});
describe('appearing timeout', () => {
it('should use enter timeout if appear not set', async () => {
let calledBeforeEntered = false;
let done = false;
setTimeout(() => {
calledBeforeEntered = true;
}, 10);
const nodeRef = React.createRef();
const { setProps } = render(
<Transition
nodeRef={nodeRef}
in={true}
timeout={{ enter: 20, exit: 10 }}
appear
>
<div ref={nodeRef} />
</Transition>
);
setProps({
onEntered() {
if (calledBeforeEntered) {
done = true;
} else {
throw new Error('wrong timeout');
}
},
});
await waitFor(() => {
expect(done).toEqual(true);
});
});
it('should use appear timeout if appear is set', async () => {
let done = false;
const nodeRef = React.createRef();
const { setProps } = render(
<Transition
nodeRef={nodeRef}
in={true}
timeout={{ enter: 20, exit: 10, appear: 5 }}
appear
>
<div ref={nodeRef} />
</Transition>
);
let isCausedLate = false;
setTimeout(() => {
isCausedLate = true;
}, 15);
setProps({
onEntered() {
if (isCausedLate) {
throw new Error('wrong timeout');
} else {
done = true;
}
},
});
await waitFor(() => {
expect(done).toEqual(true);
});
});
});
describe('entering', () => {
it('should fire callbacks', async () => {
let callOrder = [];
let done = false;
let onEnter = jest.fn(() => callOrder.push('onEnter'));
let onEntering = jest.fn(() => callOrder.push('onEntering'));
const nodeRef = React.createRef();
const { setProps } = render(
<Transition nodeRef={nodeRef} timeout={10}>
{(status) => <div ref={nodeRef}>status: {status}</div>}
</Transition>
);
expect(nodeRef.current.textContent).toEqual(`status: ${EXITED}`);
setProps({
in: true,
onEnter,
onEntering,
onEntered() {
expect(onEnter).toHaveBeenCalledTimes(1);
expect(onEntering).toHaveBeenCalledTimes(1);
expect(callOrder).toEqual(['onEnter', 'onEntering']);
done = true;
},
});
await waitFor(() => {
expect(done).toEqual(true);
});
});
it('should move to each transition state', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
<Transition nodeRef={nodeRef} timeout={10}>
{(status) => <div ref={nodeRef}>status: {status}</div>}
</Transition>
);
expect(nodeRef.current.textContent).toEqual(`status: ${EXITED}`);
setProps({
in: true,
onEnter() {
count++;
expect(nodeRef.current.textContent).toEqual(`status: ${EXITED}`);
},
onEntering() {
count++;
expect(nodeRef.current.textContent).toEqual(`status: ${ENTERING}`);
},
onEntered() {
expect(nodeRef.current.textContent).toEqual(`status: ${ENTERED}`);
},
});
await waitFor(() => {
expect(count).toEqual(2);
});
});
});
describe('exiting', () => {
it('should fire callbacks', async () => {
let callOrder = [];
let done = false;
let onExit = jest.fn(() => callOrder.push('onExit'));
let onExiting = jest.fn(() => callOrder.push('onExiting'));
const nodeRef = React.createRef();
const { setProps } = render(
<Transition nodeRef={nodeRef} in timeout={10}>
{(status) => <div ref={nodeRef}>status: {status}</div>}
</Transition>
);
expect(nodeRef.current.textContent).toEqual(`status: ${ENTERED}`);
setProps({
in: false,
onExit,
onExiting,
onExited() {
expect(onExit).toHaveBeenCalledTimes(1);
expect(onExiting).toHaveBeenCalledTimes(1);
expect(callOrder).toEqual(['onExit', 'onExiting']);
done = true;
},
});
await waitFor(() => {
expect(done).toEqual(true);
});
});
it('should move to each transition state', async () => {
let count = 0;
let done = false;
const nodeRef = React.createRef();
const { setProps } = render(
<Transition nodeRef={nodeRef} in timeout={10}>
{(status) => <div ref={nodeRef}>status: {status}</div>}
</Transition>
);
expect(nodeRef.current.textContent).toEqual(`status: ${ENTERED}`);
setProps({
in: false,
onExit() {
count++;
expect(nodeRef.current.textContent).toEqual(`status: ${ENTERED}`);
},
onExiting() {
count++;
expect(nodeRef.current.textContent).toEqual(`status: ${EXITING}`);
},
onExited() {
expect(nodeRef.current.textContent).toEqual(`status: ${EXITED}`);
expect(count).toEqual(2);
done = true;
},
});
await waitFor(() => {
expect(done).toEqual(true);
});
});
});
describe('mountOnEnter', () => {
class MountTransition extends React.Component {
nodeRef = React.createRef();
render() {
const { ...props } = this.props;
delete props.initialIn;
return (
<Transition
ref={(transition) =>
(this.transition = this.transition || transition)
}
nodeRef={this.nodeRef}
mountOnEnter
in={this.props.in}
timeout={10}
{...props}
>
{(status) => <div ref={this.nodeRef}>status: {status}</div>}
</Transition>
);
}
getStatus = () => {
return this.transition.state.status;
};
}
it('should mount when entering', (done) => {
const { container, setProps } = render(
<MountTransition
in={false}
onEnter={() => {
expect(container.textContent).toEqual(`status: ${EXITED}`);
done();
}}
/>
);
expect(container.textContent).toEqual('');
setProps({ in: true });
});
it('should stay mounted after exiting', async () => {
let entered = false;
let exited = false;
const { container, setProps } = render(
<MountTransition
in={false}
onEntered={() => {
entered = true;
}}
onExited={() => {
exited = true;
}}
/>
);
expect(container.textContent).toEqual('');
setProps({ in: true });
await waitFor(() => {
expect(entered).toEqual(true);
});
expect(container.textContent).toEqual(`status: ${ENTERED}`);
setProps({ in: false });
await waitFor(() => {
expect(exited).toEqual(true);
});
expect(container.textContent).toEqual(`status: ${EXITED}`);
});
});
describe('unmountOnExit', () => {
class UnmountTransition extends React.Component {
nodeRef = React.createRef();
render() {
const { ...props } = this.props;
delete props.initialIn;
return (
<Transition
ref={(transition) =>
(this.transition = this.transition || transition)
}
nodeRef={this.nodeRef}
unmountOnExit
in={this.props.in}
timeout={10}
{...props}
>
<div ref={this.nodeRef} />
</Transition>
);
}
getStatus = () => {
return this.transition.state.status;
};
}
it('should mount when entering', async () => {
let done = false;
const instanceRef = React.createRef();
const { setProps } = render(
<UnmountTransition
ref={instanceRef}
in={false}
onEnter={() => {
expect(instanceRef.current.getStatus()).toEqual(EXITED);
expect(instanceRef.current.nodeRef.current).toExist();
done = true;
}}
/>
);
expect(instanceRef.current.getStatus()).toEqual(UNMOUNTED);
expect(instanceRef.current.nodeRef.current).toBeNull();
setProps({ in: true });
await waitFor(() => {
expect(done).toEqual(true);
});
});
it('should unmount after exiting', async () => {
let exited = false;
const instanceRef = React.createRef();
const { setProps } = render(
<UnmountTransition
ref={instanceRef}
in
onExited={() => {
setTimeout(() => {
exited = true;
});
}}
/>
);
expect(instanceRef.current.getStatus()).toEqual(ENTERED);
expect(instanceRef.current.nodeRef.current).toExist();
setProps({ in: false });
await waitFor(() => {
expect(exited).toEqual(true);
});
expect(instanceRef.current.getStatus()).toEqual(UNMOUNTED);
expect(instanceRef.current.nodeRef.current).not.toExist();
});
});
});
================================================
FILE: test/TransitionGroup-test.js
================================================
let React;
let TransitionGroup;
let Transition;
// Most of the real functionality is covered in other unit tests, this just
// makes sure we're wired up correctly.
describe('TransitionGroup', () => {
let act, container, log, Child, renderStrict, render;
beforeEach(() => {
React = require('react');
Transition = require('../src/Transition').default;
TransitionGroup = require('../src/TransitionGroup');
const testUtils = require('./utils');
act = testUtils.act;
render = testUtils.render;
renderStrict = (element, container) =>
render(<React.StrictMode>{element}</React.StrictMode>, { container });
container = document.createElement('div');
log = [];
let events = {
onEnter: (m) => log.push(m ? 'appear' : 'enter'),
onEntering: (m) => log.push(m ? 'appearing' : 'entering'),
onEntered: (m) => log.push(m ? 'appeared' : 'entered'),
onExit: () => log.push('exit'),
onExiting: () => log.push('exiting'),
onExited: () => log.push('exited'),
};
const nodeRef = React.createRef();
Child = function Child(props) {
return (
<Transition nodeRef={nodeRef} timeout={0} {...props} {...events}>
<span ref={nodeRef} />
</Transition>
);
};
});
it('should allow null components', () => {
function FirstChild(props) {
const childrenArray = React.Children.toArray(props.children);
return childrenArray[0] || null;
}
render(
<TransitionGroup component={FirstChild}>
<Child />
</TransitionGroup>
);
});
it('should allow callback refs', () => {
const ref = jest.fn();
class Child extends React.Component {
render() {
return <span />;
}
}
render(
<TransitionGroup>
<Child ref={ref} />
</TransitionGroup>
);
expect(ref).toHaveBeenCalled();
});
it('should work with no children', () => {
renderStrict(<TransitionGroup />, container);
});
it('should handle transitioning correctly', () => {
function Parent({ count = 1 }) {
let children = [];
for (let i = 0; i < count; i++) children.push(<Child key={i} />);
return (
<TransitionGroup appear enter exit>
{children}
</TransitionGroup>
);
}
jest.useFakeTimers();
renderStrict(<Parent />, container);
act(() => {
jest.runAllTimers();
});
expect(log).toEqual(
// React 18 StrictEffects will call `componentDidMount` twice causing two `onEnter` calls.
React.useTransition !== undefined
? ['appear', 'appear', 'appearing', 'appeared']
: ['appear', 'appearing', 'appeared']
);
log = [];
renderStrict(<Parent count={2} />, container);
act(() => {
jest.runAllTimers();
});
expect(log).toEqual(
// React 18 StrictEffects will call `componentDidMount` twice causing two `onEnter` calls.
React.useTransition !== undefined
? ['enter', 'enter', 'entering', 'entered']
: ['enter', 'entering', 'entered']
);
log = [];
renderStrict(<Parent count={1} />, container);
act(() => {
jest.runAllTimers();
});
expect(log).toEqual(['exit', 'exiting', 'exited']);
});
});
================================================
FILE: test/setup.js
================================================
global.requestAnimationFrame = function (callback) {
setTimeout(callback, 0);
};
================================================
FILE: test/setupAfterEnv.js
================================================
import { cleanup } from '@testing-library/react/pure';
afterEach(() => {
cleanup();
});
================================================
FILE: test/utils.js
================================================
import { render as baseRender } from '@testing-library/react/pure';
import React from 'react';
export * from '@testing-library/react';
export function render(element, options) {
const result = baseRender(element, options);
return {
...result,
setProps(props) {
result.rerender(React.cloneElement(element, props));
},
};
}
================================================
FILE: www/.babelrc.js
================================================
module.exports = {
presets: ['babel-preset-gatsby'],
};
================================================
FILE: www/.gitignore
================================================
.cache
public
================================================
FILE: www/.npmrc
================================================
engine-strict=true
================================================
FILE: www/gatsby-config.js
================================================
const path = require('path');
module.exports = {
pathPrefix: `/react-transition-group`,
siteMetadata: {
title: 'React Transition Group Documentation',
author: 'Jason Quense',
componentPages: [
{
path: '/transition',
displayName: 'Transition',
codeSandboxId: null,
},
{
path: '/css-transition',
displayName: 'CSSTransition',
codeSandboxId: 'm77l2vp00x',
},
{
path: '/switch-transition',
displayName: 'SwitchTransition',
codeSandboxId: 'switchtransition-component-iqm0d',
},
{
path: '/transition-group',
displayName: 'TransitionGroup',
codeSandboxId: '00rqyo26kn',
},
],
},
plugins: [
'gatsby-plugin-react-helmet',
{
resolve: 'gatsby-source-filesystem',
options: {
path: path.join(__dirname, 'src/pages'),
name: 'pages',
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
path: path.join(__dirname, '../src'),
name: 'components',
},
},
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: ['gatsby-remark-prismjs'],
},
},
'gatsby-transformer-react-docgen',
'gatsby-plugin-sass',
],
};
================================================
FILE: www/gatsby-node.js
================================================
const path = require('path');
const config = require('./gatsby-config');
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions;
const componentTemplate = path.join(
__dirname,
'src',
'templates',
'component.js'
);
return new Promise((resolve, reject) => {
resolve(
graphql(`
{
allComponentMetadata {
edges {
node {
displayName
}
}
}
}
`).then((result) => {
if (result.errors) {
reject(result.errors);
}
const { componentPages } = config.siteMetadata;
result.data.allComponentMetadata.edges
.filter(({ node: { displayName } }) =>
componentPages.some((page) => page.displayName === displayName)
)
.forEach(({ node: { displayName } }) => {
createPage({
path: componentPages.find(
(page) => page.displayName === displayName
).path,
component: componentTemplate,
context: {
displayName,
},
});
});
})
);
});
};
================================================
FILE: www/package.json
================================================
{
"private": true,
"name": "react-transition-group-docs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "NODE_ENV=production gatsby build --prefix-paths",
"deploy": "npm run build && gh-pages -d public",
"develop": "gatsby develop"
},
"author": "",
"license": "MIT",
"engines": {
"node": "<=16"
},
"dependencies": {
"@babel/core": "^7.3.4",
"babel-preset-gatsby": "^2.7.0",
"bootstrap": "^4.3.1",
"gatsby": "^2.1.22",
"gatsby-plugin-react-helmet": "^3.0.10",
"gatsby-plugin-sass": "^2.0.10",
"gatsby-remark-prismjs": "^3.2.4",
"gatsby-source-filesystem": "^2.0.23",
"gatsby-transformer-react-docgen": "^3.0.5",
"gatsby-transformer-remark": "^2.3.0",
"lodash": "^4.17.19",
"prismjs": "^1.25.0",
"react": "^16.8.3",
"react-bootstrap": "^1.0.0-beta.5",
"react-dom": "^16.8.3",
"react-helmet": "^5.2.0",
"sass": "^1.49.7"
},
"devDependencies": {
"gh-pages": "^2.0.1"
}
}
================================================
FILE: www/src/components/Example.js
================================================
import React from 'react';
import PropTypes from 'prop-types';
import { Container } from 'react-bootstrap';
const propTypes = {
codeSandbox: PropTypes.shape({
title: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
}).isRequired,
};
const Example = ({ codeSandbox }) => (
<div style={{ marginBottom: '1.5rem' }}>
<Container>
<h2>Example</h2>
</Container>
<iframe
title={codeSandbox.title}
src={`https://codesandbox.io/embed/${codeSandbox.id}?fontsize=14`}
style={{
display: 'block',
width: '100%',
height: '500px',
border: 0,
borderRadius: 4,
overflow: 'hidden',
}}
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
/>
</div>
);
Example.propTypes = propTypes;
export default Example;
================================================
FILE: www/src/components/Layout.js
================================================
import { graphql, Link } from 'gatsby';
import PropTypes from 'prop-types';
import React from 'react';
import Helmet from 'react-helmet';
import { Navbar, Nav } from 'react-bootstrap';
import '../css/bootstrap.scss';
import '../css/prism-theme.scss';
const propTypes = {
data: PropTypes.shape({
site: PropTypes.shape({
siteMetadata: PropTypes.shape({
componentPages: PropTypes.arrayOf(
PropTypes.shape({
path: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
})
).isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}).isRequired,
};
const Layout = ({ data, children }) => (
<>
<Helmet>
<html lang="en" />
<title>React Transition Group</title>
</Helmet>
<Navbar fixed="top" bg="dark" variant="dark" expand="md" collapseOnSelect>
<Navbar.Brand as={Link} to="/">
React Transition Group
</Navbar.Brand>
<Navbar.Toggle />
<Navbar.Collapse>
<Nav className="mr-auto">
{data.site.siteMetadata.componentPages.map(
({ path, displayName }) => (
<Nav.Link key={path} as={Link} to={path} activeClassName="active">
{displayName}
</Nav.Link>
)
)}
</Nav>
<Nav>
<Nav.Link as={Link} to="/with-react-router" activeClassName="active">
With React Router
</Nav.Link>
<Nav.Link as={Link} to="/testing" activeClassName="active">
Testing
</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
<div style={{ paddingTop: '5rem' }}>{children}</div>
</>
);
Layout.propTypes = propTypes;
export default Layout;
export const exposedComponentsFragment = graphql`
fragment Layout_site on Site {
siteMetadata {
componentPages {
path
displayName
codeSandboxId
}
}
}
`;
================================================
FILE: www/src/css/_variables.scss
================================================
$syntax-hue: 230;
$syntax-saturation: 1%;
$syntax-brightness: 98%;
// Monochrome -----------------------------------
$mono-1: hsl($syntax-hue, 8%, 24%);
$mono-2: hsl($syntax-hue, 6%, 44%);
$mono-3: hsl($syntax-hue, 4%, 64%);
$hue-1: hsl(198, 99%, 37%); // <-cyan
$hue-2: hsl(221, 87%, 60%); // <-blue
$hue-3: hsl(301, 63%, 40%); // <-purple
$hue-4: hsl(119, 34%, 47%); // <-green
$hue-5: hsl(5, 74%, 59%); // <-red 1
$hue-5-2: hsl(344, 84%, 43%); // <-red 2
$hue-6: hsl(41, 99%, 30%); // <-orange 1
$hue-6-2: hsl(41, 99%, 38%); // <-orange 2
$syntax-fg: $mono-1;
$syntax-bg: hsl($syntax-hue, $syntax-saturation, $syntax-brightness);
$syntax-gutter: darken($syntax-bg, 36%);
$syntax-guide: fade($syntax-fg, 20%);
$syntax-accent: hsl($syntax-hue, 100%, 66%);
================================================
FILE: www/src/css/bootstrap.scss
================================================
@import '~bootstrap/dist/css/bootstrap.css';
@import 'variables.scss';
html {
font-size: 16px;
}
body {
background: $syntax-bg;
font-size: 1rem;
}
pre {
border: 0;
background: transparent;
}
code {
padding: 0;
background: transparent;
color: $hue-3;
a & {
padding: 0;
color: inherit !important;
}
}
section {
margin-bottom: 2rem;
}
h1,
h2,
h3,
h4,
h5 {
margin-bottom: 2rem;
& > a,
& > a:hover {
color: inherit;
}
}
// https://twitter.com/hakimel/status/1257252729128435712?s=20
// https://twitter.com/bramus/status/1257255545897132032?s=20
:target {
scroll-margin-top: 4rem;
}
================================================
FILE: www/src/css/prism-theme.scss
================================================
/*
Name: Base16 Atelier Sulphurpool Light
Author: Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool)
Prism template by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/prism/)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
@import 'variables.scss';
code[class*='language-'],
pre[class*='language-'] {
font-family: Consolas, Menlo, Monaco, 'Andale Mono WT', 'Andale Mono',
'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono',
'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L',
'Courier New', Courier, monospace;
font-size: 14px;
line-height: 1.375;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
background: $syntax-bg;
color: $syntax-fg;
}
pre[class*='language-']::-moz-selection,
pre[class*='language-'] ::-moz-selection,
code[class*='language-']::-moz-selection,
code[class*='language-'] ::-moz-selection {
text-shadow: none;
background: darken($syntax-bg, 8%);
}
pre[class*='language-']::selection,
pre[class*='language-'] ::selection,
code[class*='language-']::selection,
code[class*='language-'] ::selection {
text-shadow: none;
background: darken($syntax-bg, 8%);
}
/* Code blocks */
pre[class*='language-'] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
/* Inline code */
:not(pre) > code[class*='language-'] {
padding: 0.1em;
border-radius: 0.3em;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: $mono-3;
}
.token.punctuation {
color: #5e6687;
}
.token.namespace {
opacity: 0.7;
}
.token.operator,
.token.boolean,
.token.number {
color: $hue-6-2;
}
.token.property {
color: $hue-2;
}
.token.tag {
color: $hue-6;
}
.token.string {
color: $hue-4;
}
.token.function {
color: $hue-1;
}
.token.selector {
color: #6679cc;
}
.token.attr-name {
color: $hue-5;
}
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: $hue-1;
}
.token.attr-value,
.token.keyword,
.token.control,
.token.directive,
.token.unit {
color: $hue-3;
}
.token.statement,
.token.regex,
.token.atrule {
color: $hue-1;
}
.token.placeholder,
.token.variable {
color: $hue-2;
}
.token.deleted {
text-decoration: line-through;
}
.token.inserted {
border-bottom: 1px dotted #202746;
text-decoration: none;
}
.token.italic {
font-style: italic;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.important {
color: $hue-5;
}
.token.entity {
cursor: help;
}
pre > code.highlight {
outline: 0.4em solid $hue-5;
outline-offset: 0.4em;
}
/* overrides color-values for the Line Numbers plugin
* http://prismjs.com/plugins/line-numbers/
*/
.line-numbers .line-numbers-rows {
border-right-color: $mono-1;
}
.line-numbers-rows > span:before {
color: $mono-1;
}
================================================
FILE: www/src/pages/index.js
================================================
import { graphql, Link } from 'gatsby';
import PropTypes from 'prop-types';
import React from 'react';
import { Container } from 'react-bootstrap';
import Layout from '../components/Layout';
const propTypes = {
location: PropTypes.object.isRequired,
data: PropTypes.shape({
site: PropTypes.shape({
siteMetadata: PropTypes.shape({
componentPages: PropTypes.arrayOf(
PropTypes.shape({
path: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
})
).isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};
class Index extends React.Component {
render() {
const { data, location } = this.props;
return (
<Layout data={data} location={location}>
<Container>
<h1>React Transition Group</h1>
<blockquote>
<p>
Exposes simple components useful for defining entering and exiting
transitions. React Transition Group is not an animation library
like{' '}
<a href="https://github.com/chenglou/react-motion">
React-Motion
</a>
, it does not animate styles by itself. Instead it exposes
transition stages, manages classes and group elements and
manipulates the DOM in useful ways, making the implementation of
actual visual transitions much easier.
</p>
</blockquote>
<section>
<h2>Getting Started</h2>
<p />
<h3 className="h4">Installation</h3>
<pre className="language-bash">
<code>
{`
# npm
npm install react-transition-group --save
# yarn
yarn add react-transition-group
`.trim()}
</code>
</pre>
<h3 className="h4">CDN / External</h3>
<p>
Since react-transition-group is fairly small, the overhead of
including the library in your application is negligible. However,
in situations where it may be useful to benefit from an external
CDN when bundling, link to the following CDN:{' '}
<a href="https://unpkg.com/react-transition-group/dist/react-transition-group.js">
https://unpkg.com/react-transition-group/dist/react-transition-group.js
</a>
</p>
</section>
<h2>Components</h2>
<ul>
{data.site.siteMetadata.componentPages.map(
({ path, displayName }) => (
<li key={path}>
<Link to={path}>{displayName}</Link>
</li>
)
)}
</ul>
</Container>
</Layout>
);
}
}
Index.propTypes = propTypes;
export default Index;
export const pageQuery = graphql`
query HomeQuery {
site {
...Layout_site
}
}
`;
================================================
FILE: www/src/pages/testing.js
================================================
import { graphql } from 'gatsby';
import PropTypes from 'prop-types';
import React from 'react';
import { Container } from 'react-bootstrap';
import Layout from '../components/Layout';
const propTypes = {
location: PropTypes.object.isRequired,
data: PropTypes.shape({
site: PropTypes.shape({
siteMetadata: PropTypes.shape({
componentPages: PropTypes.arrayOf(
PropTypes.shape({
path: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
})
).isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};
const Testing = ({ data, location }) => (
<Layout data={data} location={location}>
<Container>
<h1>Testing Components with Transitions</h1>
<p>
In some situations, like visual snapshot testing, it's helpful to
disable transitions so they don't complicate the test, or introduce
abitrary waits. To make this easier <code>react-transition-group</code>{' '}
exposes a way to globally toggle transitions. When set,{' '}
<strong>all</strong> transitions, when toggled, will immediately switch
to their entered or exited states as appropriate.
</p>
<pre className="language-js">
<code>
{`
import { config } from 'react-transition-group'
config.disabled = true
`.trim()}
</code>
</pre>
<blockquote>
<p>
<b>Note</b>: This <strong>does not</strong> automatically disable
animations. It only disabled waits in <code>Transition</code>. You may
also have to disable animation as appropriate for the library.
example:{' '}
<a href="http://velocityjs.org/#mock">Mocking in Velocity.js</a>
</p>
</blockquote>
</Container>
</Layout>
);
Testing.propTypes = propTypes;
export default Testing;
export const pageQuery = graphql`
query TestingQuery {
site {
...Layout_site
}
}
`;
================================================
FILE: www/src/pages/with-react-router.js
================================================
import { graphql } from 'gatsby';
import PropTypes from 'prop-types';
import React from 'react';
import { Container } from 'react-bootstrap';
import Layout from '../components/Layout';
import Example from '../components/Example';
const propTypes = {
location: PropTypes.object.isRequired,
data: PropTypes.shape({
site: PropTypes.shape({
siteMetadata: PropTypes.shape({
componentPages: PropTypes.arrayOf(
PropTypes.shape({
path: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
})
).isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};
const WithReactRouter = ({ data, location }) => (
<Layout data={data} location={location}>
<Container>
<h1>Usage with React Router</h1>
<p>
People often want to animate route transitions, which can result in
delightful UX when used in moderation. The first instinct might be to
use wrap all routes in <code>TransitionGroup</code>, but that approach
requires hacks and falls apart easily when used with trickier components
of React Router like <code>Redirect</code>. You should use{' '}
<code>CSSTransition</code> for each route and manage their{' '}
<code>in</code> prop on their own.
</p>
<p>
The main challenge is the <strong>exit</strong> transition because React
Router changes to a new route instantly, so we need to keep the old
route around long enough to transition out of it. Fortunately,{' '}
<code>Route</code>
's <code>children</code> prop also accepts a <em>function</em>, which
should not be confused with the <code>render</code> prop! Unlike the{' '}
<code>render</code> prop, <code>children</code> function runs whether
the route is matched or not. React Router passes the object containing a{' '}
<code>match</code> object, which exists if the route matches, otherwise
it's <code>null</code>. This enables us to manage the <code>in</code>{' '}
prop of <code>CSSTransition</code> based on the presence of{' '}
<code>match</code>.
</p>
<p>
Exit transitions will cause the content of routes to linger until they
disappear, which might pose some styling challenges. Make sure that
routes don't affect each other's layout, for example you can remove them
from the flow of the document by using absolute or fixed positioning.
</p>
<blockquote>
<p>
<b>Note</b>: When using React Transition Group with React Router, make
sure to avoid using the <code>Switch</code> component because it only
executes the first matching <code>Route</code>. This would make the
exit transition impossible to achieve because the exiting route will
no longer match the current URL and the <code>children</code> function
won't execute.
</p>
</blockquote>
</Container>
<Example
codeSandbox={{
title: 'CSSTransition + React Router',
id: '38qm5m0mz1',
}}
/>
</Layout>
);
WithReactRouter.propTypes = propTypes;
export default WithReactRouter;
export const pageQuery = graphql`
query WithReactRouterQuery {
site {
...Layout_site
}
}
`;
================================================
FILE: www/src/templates/component.js
================================================
import { graphql } from 'gatsby';
import PropTypes from 'prop-types';
import React from 'react';
import { Container } from 'react-bootstrap';
import transform from 'lodash/transform';
import Layout from '../components/Layout';
import Example from '../components/Example';
function displayObj(obj) {
return JSON.stringify(obj, null, 2).replace(/"|'/g, '');
}
let cleanDocletValue = (str) =>
str.trim().replace(/^\{/, '').replace(/\}$/, '');
const extractMarkdown = ({ description }) =>
description &&
description.childMarkdownRemark &&
description.childMarkdownRemark.html;
const propTypes = {
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}).isRequired,
data: PropTypes.shape({
site: PropTypes.shape({
siteMetadata: PropTypes.shape({
componentPages: PropTypes.arrayOf(
PropTypes.shape({
displayName: PropTypes.string.isRequired,
codeSandboxId: PropTypes.string,
})
).isRequired,
}).isRequired,
}).isRequired,
metadata: PropTypes.shape({
displayName: PropTypes.string,
composes: PropTypes.arrayOf(PropTypes.string),
description: PropTypes.object.isRequired,
}),
}).isRequired,
};
class ComponentTemplate extends React.Component {
render() {
const { data, location } = this.props;
const { metadata } = data;
const { componentPages } = data.site.siteMetadata;
const { codeSandboxId } = componentPages.find(
(page) => page.displayName === metadata.displayName
);
return (
<Layout data={data} location={location}>
<div>
<Container>
<h1 id={metadata.displayName}>{metadata.displayName}</h1>
<div
dangerouslySetInnerHTML={{ __html: extractMarkdown(metadata) }}
/>
{codeSandboxId != null && (
<Example
codeSandbox={{
title: `${metadata.displayName} Component`,
id: codeSandboxId,
}}
/>
)}
<h2 id={`${metadata.displayName}-props`}>
<a href={`#${metadata.displayName}-props`}>Props</a>
{metadata.composes && (
<>
{' '}
<small style={{ fontStyle: 'italic', fontSize: '70%' }}>
Accepts all props from{' '}
{metadata.composes
.map((p) => (
<code key={p}>{`<${p.replace('./', '')}>`}</code>
))
.reduce((acc, el, i) => {
acc.push(el);
if (i < metadata.composes.length - 1) {
acc.push(', ');
}
return acc;
}, [])}{' '}
unless otherwise noted.
</small>
</>
)}
</h2>
{metadata.props.map((p) =>
this.renderProp(p, metadata.displayName)
)}
</Container>
</div>
</Layout>
);
}
renderProp = (prop, componentName) => {
const { defaultValue, name, required } = prop;
let typeInfo = this.renderType(prop);
let id = `${componentName}-prop-${name}`;
return (
<section key={name}>
<h3 id={id} style={{ marginTop: '1.5rem', marginBottom: '0.5rem' }}>
<a href={`#${id}`}>
<code>{name}</code>
</a>
</h3>
<div dangerouslySetInnerHTML={{ __html: extractMarkdown(prop) }} />
<div style={{ paddingLeft: 0 }}>
<div>
{'type: '}
{typeInfo && typeInfo.type === 'pre' ? (
typeInfo
) : (
<code>{typeInfo}</code>
)}
</div>
{required && <div>required</div>}
{defaultValue && (
<div>
default: <code>{defaultValue.value.trim()}</code>
</div>
)}
</div>
</section>
);
};
renderType(prop) {
let type = prop.type || {};
let name = getDisplayTypeName(type.name);
let doclets = prop.doclets || {};
switch (name) {
case 'node':
return 'any';
case 'function':
return 'Function';
case 'elementType':
return 'ReactClass<any>';
case 'dateFormat':
return 'string | (date: Date, culture: ?string, localizer: Localizer) => string';
case 'dateRangeFormat':
return '(range: { start: Date, end: Date }, culture: ?string, localizer: Localizer) => string';
case 'object':
case 'Object':
if (type.value)
return (
<pre className="shape-prop">
{displayObj(renderObject(type.value))}
</pre>
);
return name;
case 'union':
return type.value.reduce((current, val, i, list) => {
val = typeof val === 'string' ? { name: val } : val;
let item = this.renderType({ type: val });
if (React.isValidElement(item)) {
item = React.cloneElement(item, { key: i });
}
current = current.concat(item);
return i === list.length - 1 ? current : current.concat(' | ');
}, []);
case 'array':
case 'Array': {
let child = this.renderType({ type: type.value });
return (
<span>
{'Array<'}
{child}
{'>'}
</span>
);
}
case 'enum':
return this.renderEnum(type);
case 'custom':
return cleanDocletValue(doclets.type || name);
default:
return name;
}
}
renderEnum(enumType) {
const enumValues = enumType.value || [];
return <code>{enumValues.join(' | ')}</code>;
}
}
ComponentTemplate.propTypes = propTypes;
function getDisplayTypeName(typeName) {
if (typeName === 'func') {
return 'function';
} else if (typeName === 'bool') {
return 'boolean';
} else if (typeName === 'object') {
return 'Object';
}
return typeName;
}
function renderObject(props) {
return transform(
props,
(obj, val, key) => {
obj[val.required ? key : key + '?'] = simpleType(val);
},
{}
);
}
function simpleType(prop) {
let type = prop.type || {};
let name = getDisplayTypeName(type.name);
let doclets = prop.doclets || {};
switch (name) {
case 'node':
return 'any';
case 'function':
return 'Function';
case 'elementType':
return 'ReactClass<any>';
case 'object':
case 'Object':
if (type.value) return renderObject(type.value);
return name;
case 'array':
case 'Array': {
let child = simpleType({ type: type.value });
return 'Array<' + child + '>';
}
case 'custom':
return cleanDocletValue(doclets.type || name);
default:
return name;
}
}
export const query = graphql`
query ComponentMetadata($displayName: String!) {
site {
...Layout_site
}
metadata: componentMetadata(displayName: { eq: $displayName }) {
displayName
composes
description: childComponentDescription {
childMarkdownRemark {
html
}
}
props {
name
required
type {
name
value
raw
}
defaultValue {
value
computed
}
description: childComponentDescription {
childMarkdownRemark {
html
}
}
doclets
}
}
}
`;
export default ComponentTemplate;
gitextract_vqk7phwx/
├── .babelrc.js
├── .eslintignore
├── .eslintrc.yml
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.md
│ │ └── feature-request.md
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .prettierignore
├── .size-snapshot.json
├── .storybook/
│ ├── main.js
│ └── preview.js
├── CHANGELOG.md
├── LICENSE
├── Migration.md
├── README.md
├── package.json
├── prettier.config.js
├── rollup.config.js
├── src/
│ ├── CSSTransition.js
│ ├── ReplaceTransition.js
│ ├── SwitchTransition.js
│ ├── Transition.js
│ ├── TransitionGroup.js
│ ├── TransitionGroupContext.js
│ ├── config.js
│ ├── index.js
│ └── utils/
│ ├── ChildMapping.js
│ ├── PropTypes.js
│ ├── SimpleSet.js
│ └── reflow.js
├── stories/
│ ├── .eslintrc.yml
│ ├── CSSTransition.js
│ ├── CSSTransitionGroupFixture.js
│ ├── NestedTransition.js
│ ├── ReplaceTransition.js
│ ├── StoryFixture.js
│ ├── Transition.js
│ ├── TransitionGroup.js
│ ├── index.js
│ └── transitions/
│ ├── Bootstrap.js
│ ├── CSSFade.js
│ ├── CSSFadeForTransitionGroup.js
│ └── Scale.js
├── test/
│ ├── .eslintrc.yml
│ ├── CSSTransition-test.js
│ ├── CSSTransitionGroup-test.js
│ ├── ChildMapping-test.js
│ ├── SSR-test.js
│ ├── SwitchTransition-test.js
│ ├── Transition-test.js
│ ├── TransitionGroup-test.js
│ ├── setup.js
│ ├── setupAfterEnv.js
│ └── utils.js
└── www/
├── .babelrc.js
├── .gitignore
├── .npmrc
├── gatsby-config.js
├── gatsby-node.js
├── package.json
└── src/
├── components/
│ ├── Example.js
│ └── Layout.js
├── css/
│ ├── _variables.scss
│ ├── bootstrap.scss
│ └── prism-theme.scss
├── pages/
│ ├── index.js
│ ├── testing.js
│ └── with-react-router.js
└── templates/
└── component.js
SYMBOL INDEX (150 symbols across 25 files)
FILE: src/CSSTransition.js
class CSSTransition (line 86) | class CSSTransition extends React.Component {
method addClass (line 188) | addClass(node, type, phase) {
method removeClasses (line 208) | removeClasses(node, type) {
method render (line 228) | render() {
FILE: src/ReplaceTransition.js
class ReplaceTransition (line 17) | class ReplaceTransition extends React.Component {
method handleLifecycle (line 26) | handleLifecycle(handler, idx, originalArgs) {
method render (line 40) | render() {
method children (line 73) | children(props, propName) {
FILE: src/SwitchTransition.js
function areChildrenDifferent (line 6) | function areChildrenDifferent(oldChildren, newChildren) {
class SwitchTransition (line 131) | class SwitchTransition extends React.Component {
method componentDidMount (line 139) | componentDidMount() {
method getDerivedStateFromProps (line 143) | static getDerivedStateFromProps(props, state) {
method render (line 176) | render() {
FILE: src/Transition.js
constant UNMOUNTED (line 10) | const UNMOUNTED = 'unmounted';
constant EXITED (line 11) | const EXITED = 'exited';
constant ENTERING (line 12) | const ENTERING = 'entering';
constant ENTERED (line 13) | const ENTERED = 'entered';
constant EXITING (line 14) | const EXITING = 'exiting';
class Transition (line 115) | class Transition extends React.Component {
method constructor (line 118) | constructor(props, context) {
method getDerivedStateFromProps (line 150) | static getDerivedStateFromProps({ in: nextIn }, prevState) {
method componentDidMount (line 177) | componentDidMount() {
method componentDidUpdate (line 181) | componentDidUpdate(prevProps) {
method componentWillUnmount (line 199) | componentWillUnmount() {
method getTimeouts (line 203) | getTimeouts() {
method updateStatus (line 218) | updateStatus(mounting = false, nextStatus) {
method performEnter (line 242) | performEnter(mounting) {
method performExit (line 273) | performExit() {
method cancelNextCallback (line 301) | cancelNextCallback() {
method safeSetState (line 308) | safeSetState(nextState, callback) {
method setNextCallback (line 316) | setNextCallback(callback) {
method onTransitionEnd (line 335) | onTransitionEnd(timeout, handler) {
method render (line 360) | render() {
function noop (line 597) | function noop() {}
FILE: src/TransitionGroup.js
class TransitionGroup (line 32) | class TransitionGroup extends React.Component {
method constructor (line 33) | constructor(props, context) {
method componentDidMount (line 46) | componentDidMount() {
method componentWillUnmount (line 53) | componentWillUnmount() {
method getDerivedStateFromProps (line 57) | static getDerivedStateFromProps(
method handleExited (line 70) | handleExited(child, node) {
method render (line 89) | render() {
FILE: src/utils/ChildMapping.js
function getChildMapping (line 9) | function getChildMapping(children, mapFn) {
function mergeChildMappings (line 39) | function mergeChildMappings(prev, next) {
function getProp (line 84) | function getProp(child, prop, props) {
function getInitialChildMapping (line 88) | function getInitialChildMapping(props, onExited) {
function getNextChildMapping (line 100) | function getNextChildMapping(nextProps, prevChildMapping, onExited) {
FILE: src/utils/SimpleSet.js
class SimpleSet (line 1) | class SimpleSet {
method constructor (line 2) | constructor() {
method clear (line 5) | clear() {
method has (line 8) | has(k) {
method add (line 11) | add(k) {
method delete (line 15) | delete(k) {
FILE: stories/CSSTransition.js
function ToggleFixture (line 7) | function ToggleFixture({ defaultIn, description, children }) {
FILE: stories/CSSTransitionGroupFixture.js
class CSSTransitionGroupFixture (line 6) | class CSSTransitionGroupFixture extends React.Component {
method render (line 36) | render() {
FILE: stories/NestedTransition.js
function FadeAndScale (line 7) | function FadeAndScale(props) {
function Example (line 26) | function Example() {
FILE: stories/ReplaceTransition.js
constant FADE_TIMEOUT (line 8) | const FADE_TIMEOUT = 1000;
function Fade (line 47) | function Fade(props) {
function Example (line 55) | function Example({ children }) {
FILE: stories/StoryFixture.js
function StoryFixture (line 8) | function StoryFixture({ description, children }) {
FILE: stories/Transition.js
function ToggleFixture (line 12) | function ToggleFixture({ defaultIn, description, children }) {
FILE: stories/TransitionGroup.js
class DynamicTransition (line 79) | class DynamicTransition extends React.Component {
method componentDidMount (line 85) | componentDidMount() {
method componentWillUnmount (line 90) | componentWillUnmount() {
method render (line 94) | render() {
function ReEnterTransition (line 107) | function ReEnterTransition() {
FILE: stories/transitions/Bootstrap.js
function Fade (line 43) | function Fade(props) {
function getHeight (line 64) | function getHeight(elem) {
class Collapse (line 82) | class Collapse extends React.Component {
method render (line 108) | render() {
function FadeInnerRef (line 131) | function FadeInnerRef(props) {
function useMergedRef (line 165) | function useMergedRef(ref) {
FILE: stories/transitions/CSSFade.js
constant FADE_TIMEOUT (line 6) | const FADE_TIMEOUT = 1000;
function Fade (line 40) | function Fade(props) {
FILE: stories/transitions/CSSFadeForTransitionGroup.js
constant FADE_TIMEOUT (line 6) | const FADE_TIMEOUT = 1000;
function Fade (line 34) | function Fade(props) {
FILE: stories/transitions/Scale.js
constant SCALE_TIMEOUT (line 6) | const SCALE_TIMEOUT = 1000;
function Scale (line 34) | function Scale(props) {
FILE: test/CSSTransition-test.js
method onEnter (line 52) | onEnter() {
method onEntering (line 57) | onEntering() {
method onEntered (line 64) | onEntered() {
method onEnter (line 96) | onEnter() {
method onEntering (line 101) | onEntering() {
method onEntered (line 108) | onEntered() {
method onEnter (line 250) | onEnter(isAppearing) {
method onEntering (line 256) | onEntering(isAppearing) {
method onEntered (line 264) | onEntered(isAppearing) {
method onExit (line 315) | onExit() {
method onExiting (line 320) | onExiting() {
method onExited (line 327) | onExited() {
method onExit (line 358) | onExit() {
method onExiting (line 363) | onExiting() {
method onExited (line 370) | onExited() {
method onExit (line 393) | onExit() {
method onExiting (line 398) | onExiting() {
method onExited (line 403) | onExited() {
class Test (line 417) | class Test extends React.Component {
method render (line 418) | render() {
method onEnter (line 457) | onEnter() {
method onEntering (line 461) | onEntering() {
method onEntering (line 478) | onEntering() {
method onEntered (line 484) | onEntered() {
FILE: test/CSSTransitionGroup-test.js
function YoloTransition (line 16) | function YoloTransition({ id, ...props }) {
class Component (line 204) | class Component extends React.Component {
method render (line 205) | render() {
method componentDidMount (line 237) | componentDidMount() {
method render (line 241) | render() {
method render (line 279) | render() {
class Child (line 227) | class Child extends React.Component {
method render (line 228) | render() {
method render (line 273) | render() {
class Component (line 234) | class Component extends React.Component {
method render (line 205) | render() {
method componentDidMount (line 237) | componentDidMount() {
method render (line 241) | render() {
method render (line 279) | render() {
class Wrapper (line 260) | class Wrapper extends React.Component {
method render (line 261) | render() {
class Child (line 272) | class Child extends React.Component {
method render (line 228) | render() {
method render (line 273) | render() {
class Component (line 278) | class Component extends React.Component {
method render (line 205) | render() {
method componentDidMount (line 237) | componentDidMount() {
method render (line 241) | render() {
method render (line 279) | render() {
FILE: test/Transition-test.js
method toExist (line 15) | toExist(received) {
class Child (line 77) | class Child extends React.Component {
method render (line 78) | render() {
method onExited (line 186) | onExited() {
method onEntered (line 249) | onEntered() {
method onEntered (line 283) | onEntered() {
method onEntered (line 320) | onEntered() {
method onEnter (line 347) | onEnter() {
method onEntering (line 352) | onEntering() {
method onEntered (line 357) | onEntered() {
method onExited (line 390) | onExited() {
method onExit (line 418) | onExit() {
method onExiting (line 423) | onExiting() {
method onExited (line 428) | onExited() {
class MountTransition (line 442) | class MountTransition extends React.Component {
method render (line 445) | render() {
class UnmountTransition (line 519) | class UnmountTransition extends React.Component {
method render (line 522) | render() {
FILE: test/TransitionGroup-test.js
function FirstChild (line 44) | function FirstChild(props) {
class Child (line 59) | class Child extends React.Component {
method render (line 60) | render() {
function Parent (line 79) | function Parent({ count = 1 }) {
FILE: test/utils.js
function render (line 5) | function render(element, options) {
FILE: www/src/pages/index.js
class Index (line 23) | class Index extends React.Component {
method render (line 24) | render() {
FILE: www/src/templates/component.js
function displayObj (line 10) | function displayObj(obj) {
class ComponentTemplate (line 45) | class ComponentTemplate extends React.Component {
method render (line 46) | render() {
method renderType (line 137) | renderType(prop) {
method renderEnum (line 197) | renderEnum(enumType) {
function getDisplayTypeName (line 205) | function getDisplayTypeName(typeName) {
function renderObject (line 217) | function renderObject(props) {
function simpleType (line 227) | function simpleType(prop) {
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (188K chars).
[
{
"path": ".babelrc.js",
"chars": 264,
"preview": "module.exports = {\n presets: [['babel-preset-jason', { runtime: false }]],\n plugins: [\n ['babel-plugin-transform-re"
},
{
"path": ".eslintignore",
"chars": 44,
"preview": "**/node_modules\nwww/.cache/\nwww/public/\nlib\n"
},
{
"path": ".eslintrc.yml",
"chars": 300,
"preview": "parser: babel-eslint\nextends:\n - jason/react\n - plugin:jsx-a11y/recommended\n - prettier\nsettings:\n react:\n versio"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report.md",
"chars": 347,
"preview": "---\nname: Bug report\nabout: Something isn't working as expected.\n---\n\n> What is the current behavior?\n\n<!-- ... -->\n\n> W"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-request.md",
"chars": 135,
"preview": "---\nname: Feature request\nabout: I have a suggestion on how to improve the library.\n---\n\n> What would you like improved?"
},
{
"path": ".github/workflows/ci.yml",
"chars": 2251,
"preview": "name: CI\n\non:\n push:\n branches: [master, alpha]\n pull_request:\n branches: [master, alpha]\n\njobs:\n test:\n run"
},
{
"path": ".gitignore",
"chars": 628,
"preview": "/lib\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by"
},
{
"path": ".prettierignore",
"chars": 48,
"preview": "**/node_modules\nwww/.cache/\nwww/public/\nlib\n*.md"
},
{
"path": ".size-snapshot.json",
"chars": 235,
"preview": "{\n \"./lib/dist/react-transition-group.js\": {\n \"bundled\": 82684,\n \"minified\": 22426,\n \"gzipped\": 6876\n },\n \"."
},
{
"path": ".storybook/main.js",
"chars": 338,
"preview": "const { plugins, rules } = require('webpack-atoms');\n\nmodule.exports = {\n stories: ['../stories/index.js'],\n webpackFi"
},
{
"path": ".storybook/preview.js",
"chars": 142,
"preview": "import React from 'react';\n\nexport const decorators = [\n (Story) => (\n <React.StrictMode>\n <Story />\n </Reac"
},
{
"path": "CHANGELOG.md",
"chars": 14242,
"preview": "## [4.4.5](https://github.com/reactjs/react-transition-group/compare/v4.4.4...v4.4.5) (2022-08-01)\n\n\n### Bug Fixes\n\n* ap"
},
{
"path": "LICENSE",
"chars": 1608,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2018, React Community\nForked from React (https://github.com/facebook/react) Copyrigh"
},
{
"path": "Migration.md",
"chars": 7198,
"preview": "# Migration Guide from v1 to v2\n\n_A few notes to help with migrating from v1 to v2._\n\nThe `<CSSTransitionGroup>` compone"
},
{
"path": "README.md",
"chars": 1568,
"preview": "# react-transition-group [![npm][npm-badge]][npm]\n\n> **ATTENTION!** To address many issues that have come up over the ye"
},
{
"path": "package.json",
"chars": 4264,
"preview": "{\n \"name\": \"react-transition-group\",\n \"version\": \"4.4.5\",\n \"description\": \"A react component toolset for managing ani"
},
{
"path": "prettier.config.js",
"chars": 43,
"preview": "module.exports = {\n singleQuote: true,\n};\n"
},
{
"path": "rollup.config.js",
"chars": 1451,
"preview": "import nodeResolve from 'rollup-plugin-node-resolve';\nimport babel from 'rollup-plugin-babel';\nimport commonjs from 'rol"
},
{
"path": "src/CSSTransition.js",
"chars": 11881,
"preview": "import PropTypes from 'prop-types';\nimport addOneClass from 'dom-helpers/addClass';\n\nimport removeOneClass from 'dom-hel"
},
{
"path": "src/ReplaceTransition.js",
"chars": 2466,
"preview": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport TransitionGroup "
},
{
"path": "src/SwitchTransition.js",
"chars": 5750,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { ENTERED, ENTERING, EXITING } from './Transition'"
},
{
"path": "src/Transition.js",
"chars": 18414,
"preview": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport config from './"
},
{
"path": "src/TransitionGroup.js",
"chars": 5428,
"preview": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport TransitionGroupContext from './TransitionGroupCont"
},
{
"path": "src/TransitionGroupContext.js",
"chars": 70,
"preview": "import React from 'react';\n\nexport default React.createContext(null);\n"
},
{
"path": "src/config.js",
"chars": 39,
"preview": "export default {\n disabled: false,\n};\n"
},
{
"path": "src/index.js",
"chars": 358,
"preview": "export { default as CSSTransition } from './CSSTransition';\nexport { default as ReplaceTransition } from './ReplaceTrans"
},
{
"path": "src/utils/ChildMapping.js",
"chars": 4666,
"preview": "import { Children, cloneElement, isValidElement } from 'react';\n\n/**\n * Given `this.props.children`, return an object ma"
},
{
"path": "src/utils/PropTypes.js",
"chars": 890,
"preview": "import PropTypes from 'prop-types';\n\nexport const timeoutsShape =\n process.env.NODE_ENV !== 'production'\n ? PropType"
},
{
"path": "src/utils/SimpleSet.js",
"chars": 360,
"preview": "export default class SimpleSet {\n constructor() {\n this.v = [];\n }\n clear() {\n this.v.length = 0;\n }\n has(k) "
},
{
"path": "src/utils/reflow.js",
"chars": 53,
"preview": "export const forceReflow = (node) => node.scrollTop;\n"
},
{
"path": "stories/.eslintrc.yml",
"chars": 167,
"preview": "rules:\n react/prop-types: off\n no-unused-vars:\n - error\n - varsIgnorePattern: ^_$\n import/no-extraneous-depende"
},
{
"path": "stories/CSSTransition.js",
"chars": 1224,
"preview": "import React, { useState } from 'react';\nimport { storiesOf } from '@storybook/react';\n\nimport StoryFixture from './Stor"
},
{
"path": "stories/CSSTransitionGroupFixture.js",
"chars": 1693,
"preview": "import React from 'react';\n\nimport TransitionGroup from '../src/TransitionGroup';\nimport StoryFixture from './StoryFixtu"
},
{
"path": "stories/NestedTransition.js",
"chars": 1215,
"preview": "import React, { useState } from 'react';\n\nimport StoryFixture from './StoryFixture';\nimport Fade from './transitions/CSS"
},
{
"path": "stories/ReplaceTransition.js",
"chars": 2129,
"preview": "import { css } from 'astroturf';\nimport React, { useState } from 'react';\nimport { storiesOf } from '@storybook/react';\n"
},
{
"path": "stories/StoryFixture.js",
"chars": 323,
"preview": "import PropTypes from 'prop-types';\nimport React from 'react';\n\nconst propTypes = {\n description: PropTypes.string,\n};\n"
},
{
"path": "stories/Transition.js",
"chars": 1813,
"preview": "import React, { useState } from 'react';\nimport { storiesOf } from '@storybook/react';\n\nimport StoryFixture from './Stor"
},
{
"path": "stories/TransitionGroup.js",
"chars": 3501,
"preview": "import React, { useEffect, useState } from 'react';\nimport { storiesOf } from '@storybook/react';\n\nimport TransitionGrou"
},
{
"path": "stories/index.js",
"chars": 107,
"preview": "import './Transition';\nimport './CSSTransition';\nimport './TransitionGroup';\nimport './ReplaceTransition';\n"
},
{
"path": "stories/transitions/Bootstrap.js",
"chars": 3841,
"preview": "import { css } from 'astroturf';\nimport React, { useEffect, useRef } from 'react';\nimport style from 'dom-helpers/css';\n"
},
{
"path": "stories/transitions/CSSFade.js",
"chars": 926,
"preview": "import { css } from 'astroturf';\nimport React, { useRef } from 'react';\n\nimport CSSTransition from '../../src/CSSTransit"
},
{
"path": "stories/transitions/CSSFadeForTransitionGroup.js",
"chars": 814,
"preview": "import { css } from 'astroturf';\nimport React, { useRef } from 'react';\n\nimport CSSTransition from '../../src/CSSTransit"
},
{
"path": "stories/transitions/Scale.js",
"chars": 833,
"preview": "import { css } from 'astroturf';\nimport React, { useRef } from 'react';\n\nimport CSSTransition from '../../src/CSSTransit"
},
{
"path": "test/.eslintrc.yml",
"chars": 387,
"preview": "env:\n jest: true\n es6: true\nrules:\n no-require: off\n global-require: off\n no-console: off\n react/no-multi-comp: of"
},
{
"path": "test/CSSTransition-test.js",
"chars": 12370,
"preview": "import React from 'react';\nimport { render, waitFor } from './utils';\n\nimport CSSTransition from '../src/CSSTransition';"
},
{
"path": "test/CSSTransitionGroup-test.js",
"chars": 7917,
"preview": "import hasClass from 'dom-helpers/hasClass';\nimport CSSTransition from '../src/CSSTransition';\n\nlet React;\nlet ReactDOM;"
},
{
"path": "test/ChildMapping-test.js",
"chars": 2646,
"preview": "let React;\nlet ChildMapping;\n\ndescribe('ChildMapping', () => {\n beforeEach(() => {\n React = require('react');\n Ch"
},
{
"path": "test/SSR-test.js",
"chars": 253,
"preview": "/**\n * @jest-environment node\n */\n\n// test that import does not crash\nimport * as ReactTransitionGroup from '../src'; //"
},
{
"path": "test/SwitchTransition-test.js",
"chars": 4128,
"preview": "import React from 'react';\n\nimport { act, render } from './utils';\n\nimport Transition, { ENTERED } from '../src/Transiti"
},
{
"path": "test/Transition-test.js",
"chars": 14673,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport { render, waitFor } from './utils';\n\nimport Transit"
},
{
"path": "test/TransitionGroup-test.js",
"chars": 3264,
"preview": "let React;\nlet TransitionGroup;\nlet Transition;\n\n// Most of the real functionality is covered in other unit tests, this "
},
{
"path": "test/setup.js",
"chars": 83,
"preview": "global.requestAnimationFrame = function (callback) {\n setTimeout(callback, 0);\n};\n"
},
{
"path": "test/setupAfterEnv.js",
"chars": 91,
"preview": "import { cleanup } from '@testing-library/react/pure';\n\nafterEach(() => {\n cleanup();\n});\n"
},
{
"path": "test/utils.js",
"chars": 348,
"preview": "import { render as baseRender } from '@testing-library/react/pure';\nimport React from 'react';\n\nexport * from '@testing-"
},
{
"path": "www/.babelrc.js",
"chars": 58,
"preview": "module.exports = {\n presets: ['babel-preset-gatsby'],\n};\n"
},
{
"path": "www/.gitignore",
"chars": 13,
"preview": ".cache\npublic"
},
{
"path": "www/.npmrc",
"chars": 19,
"preview": "engine-strict=true\n"
},
{
"path": "www/gatsby-config.js",
"chars": 1296,
"preview": "const path = require('path');\n\nmodule.exports = {\n pathPrefix: `/react-transition-group`,\n siteMetadata: {\n title: "
},
{
"path": "www/gatsby-node.js",
"chars": 1210,
"preview": "const path = require('path');\nconst config = require('./gatsby-config');\n\nexports.createPages = ({ actions, graphql }) ="
},
{
"path": "www/package.json",
"chars": 1079,
"preview": "{\n \"private\": true,\n \"name\": \"react-transition-group-docs\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"inde"
},
{
"path": "www/src/components/Example.js",
"chars": 847,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Container } from 'react-bootstrap';\n\nconst propT"
},
{
"path": "www/src/components/Layout.js",
"chars": 2027,
"preview": "import { graphql, Link } from 'gatsby';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Helmet fro"
},
{
"path": "www/src/css/_variables.scss",
"chars": 762,
"preview": "$syntax-hue: 230;\n$syntax-saturation: 1%;\n$syntax-brightness: 98%;\n\n// Monochrome -----------------------------------\n$m"
},
{
"path": "www/src/css/bootstrap.scss",
"chars": 629,
"preview": "@import '~bootstrap/dist/css/bootstrap.css';\n@import 'variables.scss';\n\nhtml {\n font-size: 16px;\n}\n\nbody {\n background"
},
{
"path": "www/src/css/prism-theme.scss",
"chars": 3035,
"preview": "/*\nName: Base16 Atelier Sulphurpool Light\nAuthor: Bram de Haan (http://atelierbram.github.io/syntax-highlighti"
},
{
"path": "www/src/pages/index.js",
"chars": 2958,
"preview": "import { graphql, Link } from 'gatsby';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Containe"
},
{
"path": "www/src/pages/testing.js",
"chars": 1996,
"preview": "import { graphql } from 'gatsby';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Container } fr"
},
{
"path": "www/src/pages/with-react-router.js",
"chars": 3353,
"preview": "import { graphql } from 'gatsby';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Container } fr"
},
{
"path": "www/src/templates/component.js",
"chars": 7680,
"preview": "import { graphql } from 'gatsby';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Container } fr"
}
]
About this extraction
This page contains the full source code of the reactjs/react-transition-group GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (173.0 KB), approximately 46.4k tokens, and a symbol index with 150 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.