├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── example-assets ├── 2squares.gif ├── broken_flip.png ├── browserstack.png ├── compressed-demo.gif ├── corrected_flip.png ├── debugging_flip.gif ├── dropdown.gif ├── enter-exit.gif ├── enter-update-delete.gif ├── list-transition.gif ├── listanimations.gif ├── listshuffle.gif ├── nested-example.gif ├── photogrid.gif ├── rft-logo.gif ├── shrug.png ├── sort-filter.gif ├── spring-options.gif ├── spring.gif ├── square.gif ├── stripe-menu.gif ├── swipe-simple.gif └── swipe.gif ├── package.json ├── packages ├── flip-toolkit │ ├── .babelrc │ ├── @types │ │ └── rematrix.d.ts │ ├── README.md │ ├── mocha │ │ ├── test-set-up.js │ │ ├── testrunner.html │ │ └── tests.js │ ├── package.json │ ├── src │ │ ├── Flipper.ts │ │ ├── Spring │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── constants.ts │ │ ├── flip │ │ │ ├── animateFlippedElements │ │ │ │ ├── __tests__ │ │ │ │ │ ├── animateFlippedElements.domtest.js │ │ │ │ │ ├── applyStyles.domtest.js │ │ │ │ │ ├── matrix.test.ts │ │ │ │ │ └── rectInViewport.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── spring │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── spring.test.js │ │ │ │ │ └── index.ts │ │ │ │ └── types.ts │ │ │ ├── animateUnflippedElements │ │ │ │ ├── animateUnflippedElements.domtest.js │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── getFlippedElementPositions │ │ │ │ ├── getFlippedElementPositions.domtest.js │ │ │ │ ├── getFlippedElementPositionsAfterUpdate │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── getFlippedElementPositionsBeforeUpdate │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── types.ts │ │ │ │ └── utilities.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── forked-rebound │ │ │ ├── Loopers.js │ │ │ ├── README.md │ │ │ ├── Spring.js │ │ │ ├── SpringConfig.js │ │ │ ├── SpringSystem.js │ │ │ ├── index.js │ │ │ ├── onFrame.js │ │ │ ├── types.d.ts │ │ │ ├── types.ts │ │ │ └── util.js │ │ ├── index.ts │ │ ├── springSettings │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── types.ts │ │ └── utilities │ │ │ ├── index.ts │ │ │ ├── tweenProp.test.ts │ │ │ └── types.ts │ ├── test │ │ ├── index.html │ │ └── index.js │ └── tsconfig.json └── react-flip-toolkit │ ├── .babelrc │ ├── demo │ ├── CardsExample │ │ ├── Card.js │ │ ├── FocusedUser.js │ │ ├── UserGrid.js │ │ ├── index.js │ │ ├── styles.css │ │ └── userData.json │ ├── FancyDrawerSwipe │ │ └── index.js │ ├── FlipMove │ │ ├── Card.js │ │ ├── index.css │ │ └── index.js │ ├── GestureArticleSwipe │ │ └── index.js │ ├── GestureCardSwipe │ │ └── index.js │ ├── GestureSidebarExample │ │ ├── assets │ │ │ └── nighttime.jpg │ │ ├── index.js │ │ └── styles.css │ ├── GestureSidebarRightExample │ │ └── index.js │ ├── GestureStaggeredList │ │ ├── index.js │ │ └── styles.css │ ├── GuitarsExample │ │ ├── 200.html │ │ ├── GuitarItem.js │ │ ├── README.md │ │ ├── SelectedGuitar.js │ │ ├── css │ │ │ └── base.css │ │ ├── favicon.ico │ │ ├── guitarsData.js │ │ ├── img │ │ │ ├── 1.png │ │ │ ├── 10.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ ├── 9.png │ │ │ ├── electric-guitar.svg │ │ │ └── related │ │ │ │ ├── GridItemAnimation.jpg │ │ │ │ └── ImageGridEffects.png │ │ ├── index.html │ │ ├── index.js │ │ └── pater │ │ │ ├── pater.css │ │ │ └── storyblocks.jpg │ ├── HandleEnterUpdateDelete │ │ ├── getRandomList.js │ │ ├── index.js │ │ └── styles.css │ ├── ListExample │ │ ├── index.css │ │ └── index.js │ ├── NestedFlipper │ │ ├── index.css │ │ └── index.js │ ├── PhotoGridExample │ │ ├── assets │ │ │ ├── detail-1.jpg │ │ │ ├── detail-5.jpg │ │ │ ├── detail-6.jpg │ │ │ └── detail-8.jpg │ │ ├── index.css │ │ └── index.js │ ├── PortalExample │ │ ├── IconGrid │ │ │ ├── Modal.js │ │ │ └── index.js │ │ ├── assets │ │ │ ├── creative │ │ │ │ ├── art_palette.svg │ │ │ │ ├── brush.svg │ │ │ │ ├── coffee.svg │ │ │ │ ├── device.svg │ │ │ │ └── fountain_pen.svg │ │ │ └── environment │ │ │ │ ├── bio_energy.svg │ │ │ │ ├── eco_friendly_vehicle.svg │ │ │ │ ├── electronic_recycling.svg │ │ │ │ ├── energy_saving_lightbulb.svg │ │ │ │ └── global_warming.svg │ │ ├── index.js │ │ └── styles.css │ ├── RemountedFlipperExample │ │ └── index.js │ ├── RotateExample │ │ ├── index.css │ │ └── index.js │ ├── SidebarExample │ │ ├── assets │ │ │ └── nighttime.jpg │ │ ├── index.js │ │ └── styles.css │ ├── StaggeredList │ │ ├── index.js │ │ └── styles.css │ ├── TransformExample │ │ ├── index.js │ │ └── styles.css │ ├── TransformExampleExitingParent │ │ ├── index.js │ │ └── styles.css │ ├── TransformFromZeroExample │ │ ├── index.js │ │ └── styles.css │ ├── ZeroJumpExample │ │ └── index.js │ ├── assets │ │ └── dogs │ │ │ ├── australian-shepard.jpg │ │ │ ├── black-doodle.jpg │ │ │ ├── border-collie.jpg │ │ │ ├── charles-451760-unsplash.jpg │ │ │ ├── corgi.jpg │ │ │ ├── fredrik-ohlander-520696-unsplash.jpg │ │ │ ├── french-bulldog.jpg │ │ │ ├── friends.jpg │ │ │ ├── golden-with-flower.jpg │ │ │ ├── husky.jpg │ │ │ ├── ian-dooley-325434-unsplash.jpg │ │ │ ├── ipet-photo-1316903-unsplash.jpg │ │ │ ├── oscar-sutton-719939-unsplash.jpg │ │ │ ├── pug.jpg │ │ │ └── samoyed.jpg │ ├── index.html │ └── index.js │ ├── package.json │ ├── src │ ├── ExitContainer │ │ └── index.tsx │ ├── Flipped │ │ ├── Flipped.test.tsx │ │ └── index.tsx │ ├── Flipper │ │ ├── Flipper.test.tsx │ │ ├── context.ts │ │ └── index.tsx │ ├── Spring │ │ └── index.ts │ └── index.ts │ └── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint'], 4 | extends: [ 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react/recommended', 7 | ], 8 | settings: { 9 | react: { 10 | version: 'detect', 11 | }, 12 | }, 13 | rules: { 14 | indent: 'off', 15 | '@typescript-eslint/indent': 'off', 16 | '@typescript-eslint/camelcase': 'off', 17 | '@typescript-eslint/no-use-before-define': 'off', 18 | '@typescript-eslint/explicit-function-return-type': 'off', 19 | '@typescript-eslint/member-delimiter-style': 'off', 20 | '@typescript-eslint/no-non-null-assertion': 'off', 21 | '@typescript-eslint/no-explicit-any': 'off', 22 | '@typescript-eslint/explicit-member-accessibility': 'off', 23 | '@typescript-eslint/no-object-literal-type-assertion': 'off', 24 | '@typescript-eslint/array-type': 'off', 25 | '@typescript-eslint/explicit-member-accessibility': 'off', 26 | '@typescript-eslint/no-parameter-properties': 'off', 27 | '@typescript-eslint/ban-ts-ignore': 'off', 28 | '@typescript-eslint/explicit-module-boundary-types': 'off', 29 | '@typescript-eslint/ban-types': 'off', 30 | '@typescript-eslint/ban-ts-comment': 'off' 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | demo/dist 4 | es 5 | core 6 | dist 7 | npm-debug.log* 8 | yarn-error.log* 9 | TODO 10 | .cache 11 | stats.json 12 | .idea 13 | .DS_STORE 14 | .vscode 15 | .rpt2_cache* 16 | .rts2_cache* 17 | lib 18 | temp 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 17 6 | 7 | before_install: 8 | - yarn add -W codecov.io coveralls 9 | 10 | install: 11 | - yarn 12 | 13 | script: yarn run lint && yarn run test 14 | 15 | after_success: 16 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 17 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 18 | 19 | branches: 20 | only: 21 | - master 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Release Checklist 2 | 3 | Some unfortunately non-automated tasks that need to be done manually: 4 | 5 | 1. Run `yarn test` and `yarn test:dom` in the top level dir and make sure jest + mocha browser tests are passing (you'll need to check mocha tests in the browser) 6 | 2. Run `yarn format-and-fix` in the top level dir 7 | 3. Make sure readme is up-to-date 8 | 4. Verify that no unintended deps were added to `dependencies` in either package.json. 9 | 10 | 11 | ### Beta versions for testing 12 | 13 | Sometimes, you will want to test out changes before publishing them widely. In that case, do a pre-release by following these steps: 14 | 15 | 16 | 1. `npm version prepatch | preminor | premajor` 17 | 18 | 2. `npm publish --tag beta` 19 | 20 | 21 | ### Real release 22 | 23 | 1. Release `flip-toolkit`: 24 | - increment version number 25 | - run yarn build 26 | - commit with message formatted like: `git commit -m 'flip-toolkit@7.4.0'` 27 | - run npm publish 28 | 29 | Then, wait a bit (30mins+) and ensure the correct version shows up on: 30 | - npm: https://www.npmjs.com/package/flip-toolkit 31 | - unpkg: https://unpkg.com/flip-toolkit 32 | - package-phobia: https://packagephobia.com/result?p=flip-toolkit 33 | - bundle-phobia: https://bundlephobia.com/package/flip-toolkit 34 | - type declarations: https://arethetypeswrong.github.io/?p=flip-toolkit 35 | - and is working on this codesandbox which fetches the latest version of flip-toolkit: https://codesandbox.io/s/5v1k1nwz8l. Also check that type completions work in the sandbox. 36 | 37 | 2. Once everything looks good, release `react-flip-toolkit`: 38 | - `yarn upgrade flip-toolkit@^XXX` 39 | - increment version number 40 | - run yarn build 41 | - commit with message formatted like: `git commit -m 'react-flip-toolkit@7.4.0'` 42 | - run npm publish 43 | 44 | 45 | Once again, verify that everything looks good: 46 | - npm: https://www.npmjs.com/package/react-flip-toolkit 47 | - unpkg: https://unpkg.com/react-flip-toolkit 48 | - package-phobia: https://packagephobia.com/result?p=react-flip-toolkit 49 | - bundle-phobia: https://bundlephobia.com/package/react-flip-toolkit 50 | - type declarations: https://arethetypeswrong.github.io/?p=react-flip-toolkit 51 | - and is working on this codesandbox which fetches the latest version of react-flip-toolkit: https://codesandbox.io/s/8130rn9q2 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alex Holachek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example-assets/2squares.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/2squares.gif -------------------------------------------------------------------------------- /example-assets/broken_flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/broken_flip.png -------------------------------------------------------------------------------- /example-assets/browserstack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/browserstack.png -------------------------------------------------------------------------------- /example-assets/compressed-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/compressed-demo.gif -------------------------------------------------------------------------------- /example-assets/corrected_flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/corrected_flip.png -------------------------------------------------------------------------------- /example-assets/debugging_flip.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/debugging_flip.gif -------------------------------------------------------------------------------- /example-assets/dropdown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/dropdown.gif -------------------------------------------------------------------------------- /example-assets/enter-exit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/enter-exit.gif -------------------------------------------------------------------------------- /example-assets/enter-update-delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/enter-update-delete.gif -------------------------------------------------------------------------------- /example-assets/list-transition.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/list-transition.gif -------------------------------------------------------------------------------- /example-assets/listanimations.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/listanimations.gif -------------------------------------------------------------------------------- /example-assets/listshuffle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/listshuffle.gif -------------------------------------------------------------------------------- /example-assets/nested-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/nested-example.gif -------------------------------------------------------------------------------- /example-assets/photogrid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/photogrid.gif -------------------------------------------------------------------------------- /example-assets/rft-logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/rft-logo.gif -------------------------------------------------------------------------------- /example-assets/shrug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/shrug.png -------------------------------------------------------------------------------- /example-assets/sort-filter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/sort-filter.gif -------------------------------------------------------------------------------- /example-assets/spring-options.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/spring-options.gif -------------------------------------------------------------------------------- /example-assets/spring.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/spring.gif -------------------------------------------------------------------------------- /example-assets/square.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/square.gif -------------------------------------------------------------------------------- /example-assets/stripe-menu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/stripe-menu.gif -------------------------------------------------------------------------------- /example-assets/swipe-simple.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/swipe-simple.gif -------------------------------------------------------------------------------- /example-assets/swipe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/example-assets/swipe.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-flip-toolkit", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*" 6 | ], 7 | "scripts": { 8 | "test": "yarn workspaces run build && yarn workspaces run test", 9 | "test:dom": "yarn workspace flip-toolkit test:dom", 10 | "lint": "yarn workspaces run lint", 11 | "format": "yarn workspaces run format", 12 | "format-and-fix": "yarn workspaces run format-and-fix", 13 | "fix": "yarn workspaces run fix", 14 | "check-types": "yarn workspaces run check-types", 15 | "build": "yarn workspaces run build", 16 | "build:debug": "yarn workspaces run build:debug", 17 | "demo": "yarn workspace react-flip-toolkit start", 18 | "watch": "yarn workspaces run watch" 19 | }, 20 | "version": "7.2.4" 21 | } 22 | -------------------------------------------------------------------------------- /packages/flip-toolkit/.babelrc: -------------------------------------------------------------------------------- 1 | // just for tests + parcel demo 2 | { 3 | "presets": [ 4 | "@babel/preset-typescript", 5 | "@babel/preset-react", 6 | "@babel/preset-env" 7 | ], 8 | "plugins": ["@babel/plugin-proposal-class-properties"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/flip-toolkit/@types/rematrix.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rematrix' { 2 | export function parse(string: string): number[] 3 | export function translateX(num: number): number[] 4 | export function translateY(num: number): number[] 5 | export function scaleX(num: number): number[] 6 | export function scaleY(num: number): number[] 7 | export function multiply(arr1: number[], arr2: number[]): number[] 8 | } 9 | 10 | -------------------------------------------------------------------------------- /packages/flip-toolkit/README.md: -------------------------------------------------------------------------------- 1 | # flip-toolkit 2 | 3 | [![Minified & Gzipped size](https://badgen.net/bundlephobia/minzip/flip-toolkit)](https://bundlephobia.com/result?p=flip-toolkit) 4 | [![MIT license](https://badgen.net/npm/license/react-flip-toolkit)](http://opensource.org/licenses/MIT) 5 | [![npm version](https://badgen.net/npm/v/flip-toolkit)](https://npmjs.org/package/flip-toolkit 'View this project on npm') 6 | 7 | Using `Vue.js` ? Try [vue-flip-toolkit](https://github.com/mattrothenberg/vue-flip-toolkit). 8 | 9 | ## Basic Example 10 | 11 | [**Fork this example on CodeSandbox**](https://codesandbox.io/s/5v1k1nwz8l) 12 | 13 | ```js 14 | import { Flipper } from 'flip-toolkit' 15 | const container = document.querySelector('.container') 16 | const square = document.querySelector('.square') 17 | const innerSquare = document.querySelector('.inner-square') 18 | 19 | const flipper = new Flipper({ element: container }) 20 | 21 | // add flipped children to the parent 22 | flipper.addFlipped({ 23 | element: square, 24 | // assign a unique id to the element 25 | flipId: 'square', 26 | onStart: () => console.log('animation started!'), 27 | onSpringUpdate: springValue => 28 | console.log(`current spring value: ${springValue}`), 29 | onComplete: () => console.log('animation completed!') 30 | }) 31 | 32 | // to add an inverted child 33 | // (so that the text doesn't warp) 34 | // use this method with 35 | // a reference to the parent element 36 | flipper.addInverted({ 37 | element: innerSquare, 38 | parent: square 39 | }) 40 | 41 | square.addEventListener('click', () => { 42 | // record positions before they change 43 | flipper.recordBeforeUpdate() 44 | square.classList.toggle('big-square') 45 | // record new positions, and begin animations 46 | flipper.update() 47 | }) 48 | ``` 49 | 50 | To learn more about which configuration options are available, [check out the code for the `Flipper` class here](../react-flip-toolkit/src/FlipToolkit/Flipper.ts) or [read the docs for `react-flip-toolkit`](../react-flip-toolkit/README.md) 51 | 52 | ## Spring 53 | 54 | `flip-toolkit` also exports a utility function, `spring`, that can be used to orchestrate non-FLIP animations. 55 | 56 | [**Fork this example on CodeSandbox**](https://codesandbox.io/s/spring-example-6xw5p) 57 | 58 | ```js 59 | import { spring } from "flip-toolkit"; 60 | 61 | const container = document.querySelector(".container"); 62 | const squares = [...container.querySelectorAll(".square")]; 63 | 64 | squares.forEach((el, i) => { 65 | spring({ 66 | config: "wobbly", 67 | values: { 68 | translateY: [-15, 0], 69 | opacity: [0, 1] 70 | }, 71 | onUpdate: ({ translateY, opacity }) => { 72 | el.style.opacity = opacity; 73 | el.style.transform = `translateY(${translateY}px)`; 74 | }, 75 | delay: i * 25, 76 | onComplete: () => { 77 | // add callback logic here if necessary 78 | } 79 | }); 80 | }); 81 | ``` 82 | -------------------------------------------------------------------------------- /packages/flip-toolkit/mocha/test-set-up.js: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime' 2 | // mocha will be global 3 | require("mocha") 4 | const chai = require("chai") 5 | // we need a global "expect" function too 6 | window.expect = chai.expect 7 | mocha.setup("bdd") 8 | -------------------------------------------------------------------------------- /packages/flip-toolkit/mocha/testrunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DOM Tests 5 | 6 | 7 | 8 | 9 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /packages/flip-toolkit/mocha/tests.js: -------------------------------------------------------------------------------- 1 | import '../src/flip/animateFlippedElements/__tests__/applyStyles.domtest.js' 2 | import '../src/flip/animateFlippedElements/__tests__/animateFlippedElements.domtest.js' 3 | import '../src/flip/animateUnflippedElements/animateUnflippedElements.domtest.js' 4 | import '../src/flip/getFlippedElementPositions/getFlippedElementPositions.domtest.js' 5 | mocha.run() // eslint-disable-line 6 | -------------------------------------------------------------------------------- /packages/flip-toolkit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flip-toolkit", 3 | "version": "7.2.4", 4 | "description": "Configurable FLIP animation helpers", 5 | "license": "MIT", 6 | "src": "src/index.ts", 7 | "main": "lib/index.js", 8 | "umd:main": "lib/index.umd.js", 9 | "module": "lib/index.es.js", 10 | "types": "./lib/index.d.ts", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/aholachek/react-flip-toolkit" 14 | }, 15 | "bugs": "https://github.com/aholachek/react-flip-toolkit/issues", 16 | "author": "Alex Holachek", 17 | "keywords": [ 18 | "FLIP", 19 | "transition", 20 | "animation" 21 | ], 22 | "engines": { 23 | "node": ">=8", 24 | "npm": ">=5" 25 | }, 26 | "scripts": { 27 | "build": "microbundle --name=FlipToolkit --define process.env.NODE_ENV=production --globals rematrix=Rematrix", 28 | "build:debug": "microbundle --name=FlipToolkit --no-compress", 29 | "lint": "echo \"noop\"", 30 | "test": "echo \"noop\"", 31 | "test:dom": "parcel mocha/testrunner.html", 32 | "start": "parcel test/index.html", 33 | "prepare": "npm run build", 34 | "format": "npx prettier --write 'src/**/*.{ts,tsx}'", 35 | "fix": "eslint src/**/*.ts --fix", 36 | "format-and-fix": "npm-run-all format fix" 37 | }, 38 | "dependencies": { 39 | "rematrix": "0.2.2" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.24.7", 43 | "chai": "^4.4.1", 44 | "microbundle": "^0.15.1", 45 | "mocha": "^8.4.0", 46 | "parcel": "^1.12.4", 47 | "prettier": "2.8.8" 48 | }, 49 | "files": [ 50 | "lib" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/Spring/index.ts: -------------------------------------------------------------------------------- 1 | // this is exclusively for users of the library to create their own enter + exit animations 2 | import { SpringSystem } from '../forked-rebound' 3 | import { SpringSystemInterface } from '../forked-rebound/types.d' 4 | import { tweenProp, assign } from '../utilities' 5 | import { normalizeSpring, springPresets } from '../springSettings' 6 | import { SimpleSpringOptions } from './types' 7 | import { SpringConfig } from '../springSettings/types' 8 | 9 | // this should get created only 1x 10 | const springSystem: SpringSystemInterface = new SpringSystem() 11 | 12 | /** 13 | * A simple spring function for animating DOM properties. 14 | * Returns a function that will immediately cancel the in-progress animation. 15 | * */ 16 | const createSimpleSpring = ({ 17 | config, 18 | values, 19 | onUpdate, 20 | delay = 0, 21 | onComplete 22 | }: SimpleSpringOptions) => { 23 | const { stiffness, damping, overshootClamping } = assign( 24 | {}, 25 | springPresets.noWobble, 26 | normalizeSpring(config) 27 | ) as SpringConfig 28 | const spring = springSystem.createSpring(stiffness!, damping!) 29 | spring.setOvershootClampingEnabled(!!overshootClamping) 30 | spring.addListener({ 31 | onSpringAtRest: spring => { 32 | if (onComplete) onComplete() 33 | spring.destroy() 34 | }, 35 | onSpringUpdate: spring => { 36 | const springVal = spring.getCurrentValue() 37 | if (!values) return onUpdate(springVal) 38 | const currentValues = Object.keys(values) 39 | .map(value => [ 40 | value, 41 | tweenProp(values[value][0], values[value][1], springVal) 42 | ]) 43 | .reduce((acc, curr) => { 44 | return Object.assign(acc, { [curr[0]]: curr[1] }) 45 | }, {}) 46 | onUpdate(currentValues) 47 | } 48 | }) 49 | if (delay) { 50 | setTimeout(() => { 51 | spring.setEndValue(1) 52 | }, delay) 53 | } else { 54 | spring.setEndValue(1) 55 | } 56 | return spring 57 | } 58 | 59 | export default createSimpleSpring 60 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/Spring/types.ts: -------------------------------------------------------------------------------- 1 | import { SpringOption } from '../springSettings/types' 2 | 3 | type TweenStart = number 4 | type TweenEnd = number 5 | type Values = Record 6 | 7 | type SpringSnapshot = Record 8 | 9 | export interface SimpleSpringOptions { 10 | /** Provide a string referencing one of the spring presets — noWobble (default), veryGentle, gentle, wobbly, or stiff, OR provide an object with stiffness and damping parameters. */ 11 | config?: SpringOption 12 | /** An object like: { opacity: [0, 1], translateY: [-30, 0]} */ 13 | values?: Values 14 | /** If you provided no values argument, this will be called with the current spring value (between 0-1). Otherwise, this will be an object with keys corresponding to the values argument you passed in, an object like: { opacity: .5, translateY: -15 } */ 15 | onUpdate: (value: number | SpringSnapshot) => void 16 | /** Number of milliseconds to wait before the animation is started */ 17 | delay?: number 18 | /** Function to be called when spring is at rest */ 19 | onComplete?: () => void 20 | } 21 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DATA_FLIP_ID = 'data-flip-id' 2 | export const DATA_INVERSE_FLIP_ID = 'data-inverse-flip-id' 3 | export const DATA_FLIP_COMPONENT_ID = 'data-flip-component-id' 4 | export const DATA_FLIP_CONFIG = 'data-flip-config' 5 | export const DATA_PORTAL_KEY = 'data-portal-key' 6 | export const DATA_EXIT_CONTAINER = 'data-exit-container' 7 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/animateFlippedElements/__tests__/matrix.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | convertMatrix3dArrayTo2dArray, 3 | convertMatrix2dArrayToString 4 | } from '../index' 5 | 6 | describe('convertMatrix3dArrayTo2dArray', () => { 7 | it('takes a 3d matrix and returns a 2d one', () => { 8 | const threeDArray = [1, 7, 8, 13, 4, 1, 33, 9, 11, 7, 1, 0, 344, 10, 4, 1] 9 | expect(convertMatrix3dArrayTo2dArray(threeDArray)).toEqual([ 10 | 1, 7, 4, 1, 344, 10 11 | ]) 12 | }) 13 | }) 14 | 15 | describe('convertMatrix2dArrayToString', () => { 16 | it('takes the values and makes a string', () => { 17 | expect( 18 | convertMatrix2dArrayToString([ 19 | 0.2215909090909091, 0, 0, 0.5113636363636364, -376, 267.28125 20 | ]) 21 | ).toEqual( 22 | 'matrix(0.2215909090909091, 0, 0, 0.5113636363636364, -376, 267.28125)' 23 | ) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/animateFlippedElements/__tests__/rectInViewport.test.ts: -------------------------------------------------------------------------------- 1 | import { rectInViewport } from '../index' 2 | 3 | Object.defineProperty(window, 'innerHeight', { 4 | value: 100, 5 | writable: true 6 | }) 7 | 8 | Object.defineProperty(window, 'innerWidth', { 9 | value: 100, 10 | writable: true 11 | }) 12 | 13 | describe('rectInViewport', () => { 14 | it('returns true if rect is in viewport', () => { 15 | // @ts-ignore 16 | expect(rectInViewport({ top: 1, bottom: 99, left: 1, right: 99 })).toBe( 17 | true 18 | ) 19 | }) 20 | 21 | it('if rect isnt in viewport, returns false', () => { 22 | // @ts-ignore 23 | expect(rectInViewport({ top: 100, bottom: 101, left: 1, right: 99 })).toBe( 24 | false 25 | ) 26 | // @ts-ignore 27 | expect(rectInViewport({ top: -1, bottom: 0, left: 1, right: 99 })).toBe( 28 | false 29 | ) 30 | // @ts-ignore 31 | 32 | expect(rectInViewport({ top: 1, bottom: 99, left: 100, right: 101 })).toBe( 33 | false 34 | ) 35 | // @ts-ignore 36 | 37 | expect(rectInViewport({ top: 1, bottom: 99, left: -1, right: 0 })).toBe( 38 | false 39 | ) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/animateFlippedElements/spring/__tests__/spring.test.js: -------------------------------------------------------------------------------- 1 | import { normalizeSpeed } from '../index' 2 | 3 | describe('normalizeSpeed', () => { 4 | it('if not provided a number, it returns 1.1', () => { 5 | expect(normalizeSpeed(null)).toBe(1.1) 6 | expect(normalizeSpeed(undefined)).toBe(1.1) 7 | expect(normalizeSpeed('string')).toBe(1.1) 8 | }) 9 | it('if provided 1, returns 6', () => { 10 | expect(normalizeSpeed(1)).toBe(6) 11 | }) 12 | 13 | it('if provided 10, returns 6', () => { 14 | expect(normalizeSpeed(10)).toBe(6) 15 | }) 16 | 17 | it('if provided 0, returns 1', () => { 18 | expect(normalizeSpeed(0)).toBe(1) 19 | }) 20 | 21 | it('if provided -1, returns 1', () => { 22 | expect(normalizeSpeed(-1)).toBe(1) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/animateFlippedElements/spring/index.ts: -------------------------------------------------------------------------------- 1 | import { SpringSystem } from '../../../forked-rebound' 2 | import { StaggerConfigValue } from '../../../types' 3 | import { FlipData, FlipDataArray } from '../types' 4 | import { 5 | SpringSystemInterface, 6 | AddListenerArgs 7 | } from '../../../forked-rebound/types' 8 | 9 | // this should get created only 1x 10 | const springSystem: SpringSystemInterface = new SpringSystem() 11 | 12 | export const createSuspendedSpring = (flipData: FlipData) => { 13 | const { 14 | springConfig: { stiffness, damping, overshootClamping }, 15 | getOnUpdateFunc, 16 | onAnimationEnd, 17 | onSpringActivate 18 | } = flipData 19 | 20 | const spring = springSystem.createSpring(stiffness!, damping!) 21 | spring.setOvershootClampingEnabled(!!overshootClamping) 22 | const onSpringAtRest = () => { 23 | // prevent SpringSystem from caching unused springs 24 | spring.destroy() 25 | onAnimationEnd() 26 | } 27 | 28 | const springConfig: AddListenerArgs = { 29 | onSpringActivate, 30 | onSpringAtRest, 31 | onSpringUpdate: getOnUpdateFunc({ 32 | spring, 33 | onAnimationEnd 34 | }) 35 | } 36 | 37 | spring.addListener(springConfig) 38 | return spring 39 | } 40 | 41 | export const createSpring = (flipped: FlipData) => { 42 | const spring = createSuspendedSpring(flipped) 43 | spring.setEndValue(1) 44 | return spring 45 | } 46 | 47 | export const normalizeSpeed = (speedConfig: number | undefined) => { 48 | if (typeof speedConfig !== 'number') return 1.1 49 | return 1 + Math.min(Math.max(speedConfig * 5, 0), 5) 50 | } 51 | 52 | export const createStaggeredSprings = ( 53 | flippedArray: FlipDataArray, 54 | staggerConfig: StaggerConfigValue = {} 55 | ) => { 56 | if (!flippedArray || !flippedArray.length) { 57 | return 58 | } 59 | 60 | if (staggerConfig.reverse) { 61 | flippedArray.reverse() 62 | } 63 | 64 | const normalizedSpeed = normalizeSpeed(staggerConfig.speed) 65 | 66 | const nextThreshold = 1 / Math.max(Math.min(flippedArray.length, 100), 10) 67 | 68 | const setEndValueFuncs = flippedArray 69 | .map((flipped, i) => { 70 | const cachedGetOnUpdate = flipped.getOnUpdateFunc 71 | 72 | // modify the update function to adjust 73 | // the end value of the trailing Flipped component 74 | flipped.getOnUpdateFunc = args => { 75 | const onUpdate = cachedGetOnUpdate(args) 76 | return spring => { 77 | let currentValue = spring.getCurrentValue() 78 | // make sure trailing animations complete 79 | currentValue = 80 | currentValue < 0.01 ? 0 : currentValue > 0.99 ? 1 : currentValue 81 | 82 | const updateTrailingAnimation = currentValue >= nextThreshold 83 | if (updateTrailingAnimation) { 84 | if (setEndValueFuncs[i + 1]) { 85 | setEndValueFuncs[i + 1]!( 86 | Math.max(Math.min(currentValue * normalizedSpeed, 1), 0) 87 | ) 88 | } 89 | } 90 | // now call the actual update function 91 | onUpdate(spring) 92 | } 93 | } 94 | return flipped 95 | }) 96 | .map(flipped => { 97 | const spring = createSuspendedSpring(flipped) 98 | if (!spring) { 99 | return 100 | } 101 | return spring.setEndValue.bind(spring) 102 | }) 103 | .filter(Boolean) 104 | 105 | if (setEndValueFuncs[0]) { 106 | setEndValueFuncs[0]!(1) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/animateFlippedElements/types.ts: -------------------------------------------------------------------------------- 1 | import { BaseFlipArgs, FlippedIds } from '../types' 2 | import { SpringOption, SpringConfig } from '../../springSettings/types' 3 | import { StaggerConfig, OnFlipperComplete, FlipId } from '../../types' 4 | import { SerializableFlippedProps } from '../../types' 5 | import { Spring } from '../../forked-rebound/types' 6 | 7 | export type ScopedSelector = (selector: string) => HTMLElement[] 8 | 9 | export interface AnimateFlippedElementsArgs extends BaseFlipArgs { 10 | flippedIds: FlippedIds 11 | applyTransformOrigin: boolean 12 | spring: SpringOption 13 | debug: boolean 14 | staggerConfig: StaggerConfig 15 | decisionData: any 16 | scopedSelector: ScopedSelector 17 | onComplete: OnFlipperComplete 18 | containerEl: HTMLElement 19 | } 20 | 21 | export type OnUpdate = (spring: Spring) => void 22 | 23 | export type GetOnUpdateFunc = ({ 24 | spring, 25 | onAnimationEnd 26 | }: { 27 | spring: Spring 28 | onAnimationEnd: () => void 29 | }) => OnUpdate 30 | 31 | export type Matrix = number[] 32 | 33 | export type InvertedChild = [ 34 | HTMLElement, 35 | Omit 36 | ] 37 | 38 | export type InvertedChildren = InvertedChild[] 39 | 40 | export interface AnimatedVals { 41 | matrix: Matrix 42 | opacity?: number 43 | } 44 | 45 | export type InitializeFlip = () => void 46 | 47 | export type ChildIds = string[] 48 | 49 | export interface StaggeredChildren { 50 | // group by stagger key 51 | [stagger: string]: FlipDataArray 52 | } 53 | 54 | export interface FlipData { 55 | element: HTMLElement 56 | id: string 57 | stagger: string 58 | springConfig: SpringConfig 59 | getOnUpdateFunc: GetOnUpdateFunc 60 | initializeFlip: InitializeFlip 61 | onAnimationEnd: () => void 62 | childIds: ChildIds 63 | delayUntil?: FlipId 64 | onSpringActivate?: () => void 65 | } 66 | export type FlipDataArray = FlipData[] 67 | 68 | export interface FlipDataDict { 69 | [flipId: string]: FlipData 70 | } 71 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/animateUnflippedElements/index.ts: -------------------------------------------------------------------------------- 1 | import { AnimateUnflippedElementsArgs, FragmentTuple } from './types' 2 | 3 | const animateUnflippedElements = ({ 4 | unflippedIds, 5 | flipCallbacks, 6 | getElement, 7 | flippedElementPositionsBeforeUpdate, 8 | flippedElementPositionsAfterUpdate, 9 | inProgressAnimations, 10 | decisionData 11 | }: AnimateUnflippedElementsArgs) => { 12 | const enteringElementIds = unflippedIds.filter( 13 | id => flippedElementPositionsAfterUpdate[id] 14 | ) 15 | const animatedEnteringElementIds = enteringElementIds.filter( 16 | id => flipCallbacks[id] && flipCallbacks[id].onAppear 17 | ) 18 | 19 | const animatedExitingElementIds = unflippedIds.filter( 20 | id => 21 | flippedElementPositionsBeforeUpdate[id] && 22 | flipCallbacks[id] && 23 | flipCallbacks[id].onExit 24 | ) 25 | 26 | const hideEnteringElements = () => { 27 | animatedEnteringElementIds.forEach(id => { 28 | const element = getElement(id) 29 | if (element) { 30 | element.style.opacity = '0' 31 | } 32 | }) 33 | } 34 | 35 | const animateEnteringElements = () => { 36 | animatedEnteringElementIds.forEach((id, i) => { 37 | const element = getElement(id) 38 | if (element) { 39 | flipCallbacks[id].onAppear!(element, i, decisionData) 40 | } 41 | }) 42 | } 43 | 44 | let closureResolve: () => void 45 | 46 | const promiseToReturn: Promise = new Promise(resolve => { 47 | closureResolve = resolve 48 | }) 49 | 50 | const fragmentTuples: FragmentTuple[] = [] 51 | let exitingElementCount = 0 52 | 53 | const onExitCallbacks = animatedExitingElementIds.map((id, i) => { 54 | const { 55 | domDataForExitAnimations: { 56 | element, 57 | parent, 58 | childPosition: { top, left, width, height } 59 | } 60 | } = flippedElementPositionsBeforeUpdate[id] 61 | // insert back into dom 62 | if (getComputedStyle(parent).position === 'static') { 63 | parent.style.position = 'relative' 64 | } 65 | element.style.transform = 'matrix(1, 0, 0, 1, 0, 0)' 66 | element.style.position = 'absolute' 67 | element.style.top = top + 'px' 68 | element.style.left = left + 'px' 69 | // taken out of the dom flow, the element might have lost these dimensions 70 | element.style.height = height + 'px' 71 | element.style.width = width + 'px' 72 | let fragmentTuple: FragmentTuple | undefined = fragmentTuples.filter( 73 | t => t[0] === parent 74 | )[0] 75 | if (!fragmentTuple) { 76 | fragmentTuple = [parent, document.createDocumentFragment()] 77 | fragmentTuples.push(fragmentTuple) 78 | } 79 | fragmentTuple[1].appendChild(element) 80 | 81 | exitingElementCount += 1 82 | 83 | const stop = () => { 84 | try { 85 | parent.removeChild(element) 86 | } catch (DOMException) { 87 | // the element is already gone 88 | } finally { 89 | exitingElementCount -= 1 90 | if (exitingElementCount === 0) { 91 | closureResolve() 92 | } 93 | } 94 | } 95 | // @ts-ignore 96 | inProgressAnimations[id] = { stop } 97 | return () => flipCallbacks[id].onExit!(element, i, stop, decisionData) 98 | }) 99 | 100 | // now append all the fragments from the onExit callbacks 101 | // (we use fragments for performance) 102 | fragmentTuples.forEach(t => { 103 | t[0].appendChild(t[1]) 104 | }) 105 | 106 | if (!onExitCallbacks.length) { 107 | closureResolve!() 108 | } 109 | 110 | const animateExitingElements = () => { 111 | onExitCallbacks.forEach(c => c()) 112 | return promiseToReturn 113 | } 114 | 115 | return { 116 | hideEnteringElements, 117 | animateEnteringElements, 118 | animateExitingElements 119 | } 120 | } 121 | 122 | export default animateUnflippedElements 123 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/animateUnflippedElements/types.ts: -------------------------------------------------------------------------------- 1 | import { BaseFlipArgs } from '../types' 2 | 3 | export interface AnimateUnflippedElementsArgs extends BaseFlipArgs { 4 | unflippedIds: string[] 5 | } 6 | 7 | export type FragmentTuple = [HTMLElement, DocumentFragment] 8 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/getFlippedElementPositions/getFlippedElementPositionsAfterUpdate/index.ts: -------------------------------------------------------------------------------- 1 | import { addTupleToObject, getRects, getAllElements } from '../utilities' 2 | import { 3 | FlippedElementPositionsAfterUpdate, 4 | FlippedElementPositionDatumAfterUpdate 5 | } from './types' 6 | 7 | const getFlippedElementPositionsAfterUpdate = ({ 8 | element, 9 | portalKey 10 | }: { 11 | element: HTMLElement 12 | portalKey?: string 13 | }): FlippedElementPositionsAfterUpdate => { 14 | const positionArray = getRects(getAllElements(element, portalKey)).map( 15 | ([child, childBCR]) => { 16 | const computedStyle = window.getComputedStyle(child) 17 | return [ 18 | child.dataset.flipId, 19 | { 20 | element: child, 21 | rect: childBCR, 22 | opacity: parseFloat(computedStyle.opacity!), 23 | transform: computedStyle.transform 24 | } 25 | ] 26 | } 27 | ) as [string, FlippedElementPositionDatumAfterUpdate][] 28 | 29 | return positionArray.reduce(addTupleToObject, {}) 30 | } 31 | 32 | export default getFlippedElementPositionsAfterUpdate 33 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/getFlippedElementPositions/getFlippedElementPositionsAfterUpdate/types.ts: -------------------------------------------------------------------------------- 1 | import { BaseFlippedElementPositions } from '../types' 2 | 3 | export interface FlippedElementPositionDatumAfterUpdate 4 | extends BaseFlippedElementPositions { 5 | transform: string 6 | element: HTMLElement 7 | } 8 | 9 | export interface FlippedElementPositionsAfterUpdate { 10 | [key: string]: FlippedElementPositionDatumAfterUpdate 11 | } 12 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate/index.ts: -------------------------------------------------------------------------------- 1 | import { addTupleToObject, getAllElements, getRects } from '../utilities' 2 | import * as constants from '../../../constants' 3 | import { toArray, assign } from '../../../utilities' 4 | import { 5 | FlippedElementPositionsBeforeUpdateReturnVals, 6 | FlippedElementPositionDatumBeforeUpdate, 7 | GetFlippedElementPositionsBeforeUpdateArgs, 8 | ParentBCRs, 9 | ChildIdsToParentBCRs, 10 | ChildIdsToParents 11 | } from './types' 12 | import { InProgressAnimations } from '../../../types' 13 | 14 | export const cancelInProgressAnimations = ( 15 | inProgressAnimations: InProgressAnimations, 16 | animatingElements: HTMLElement[] 17 | ) => { 18 | Object.keys(inProgressAnimations).forEach(id => { 19 | if (inProgressAnimations[id].destroy) { 20 | inProgressAnimations[id].destroy!() 21 | } 22 | if (inProgressAnimations[id].onAnimationEnd) { 23 | inProgressAnimations[id].onAnimationEnd!(true) 24 | } 25 | delete inProgressAnimations[id] 26 | }) 27 | animatingElements.forEach(el => { 28 | el.style.transform = '' 29 | el.style.opacity = '' 30 | }) 31 | } 32 | 33 | const getFlippedElementPositionsBeforeUpdate = ({ 34 | element, 35 | flipCallbacks = {}, 36 | inProgressAnimations = {}, 37 | portalKey 38 | }: GetFlippedElementPositionsBeforeUpdateArgs): FlippedElementPositionsBeforeUpdateReturnVals => { 39 | const flippedElements = getAllElements(element, portalKey) 40 | 41 | const inverseFlippedElements = toArray( 42 | element.querySelectorAll(`[${constants.DATA_INVERSE_FLIP_ID}]`) 43 | ) 44 | 45 | const childIdsToParentBCRs: ChildIdsToParentBCRs = {} 46 | const parentBCRs: ParentBCRs = [] 47 | const childIdsToParents: ChildIdsToParents = {} 48 | // this is for exit animations so we can re-insert exiting elements in the 49 | // DOM later 50 | flippedElements 51 | .filter( 52 | el => 53 | flipCallbacks && 54 | flipCallbacks[el.dataset.flipId!] && 55 | flipCallbacks[el.dataset.flipId!].onExit 56 | ) 57 | .forEach(el => { 58 | let parent = el.parentNode as HTMLElement 59 | // this won't work for IE11 60 | if (el.closest) { 61 | const exitContainer = el.closest( 62 | `[${constants.DATA_EXIT_CONTAINER}]` 63 | ) as HTMLElement 64 | if (exitContainer) { 65 | parent = exitContainer 66 | } 67 | } 68 | let bcrIndex = parentBCRs.findIndex(n => n[0] === parent) 69 | if (bcrIndex === -1) { 70 | parentBCRs.push([parent, parent.getBoundingClientRect()]) 71 | bcrIndex = parentBCRs.length - 1 72 | } 73 | childIdsToParentBCRs[el.dataset.flipId!] = parentBCRs[bcrIndex][1] 74 | childIdsToParents[el.dataset.flipId!] = parent 75 | }) 76 | 77 | const filteredFlippedElements = getRects(flippedElements) 78 | 79 | const flippedElementPositionsTupleArray: [ 80 | string, 81 | FlippedElementPositionDatumBeforeUpdate 82 | ][] = filteredFlippedElements.map(([child, childBCR]) => { 83 | const domDataForExitAnimations = {} 84 | 85 | // only cache extra data for exit animations 86 | // if the element has an onExit listener 87 | if ( 88 | flipCallbacks && 89 | flipCallbacks[child.dataset.flipId!] && 90 | flipCallbacks[child.dataset.flipId!].onExit 91 | ) { 92 | const parentBCR = childIdsToParentBCRs[child.dataset.flipId!] 93 | 94 | assign(domDataForExitAnimations, { 95 | element: child, 96 | parent: childIdsToParents[child.dataset.flipId!], 97 | childPosition: { 98 | top: childBCR.top - parentBCR.top, 99 | left: childBCR.left - parentBCR.left, 100 | width: childBCR.width, 101 | height: childBCR.height 102 | } 103 | }) 104 | } 105 | 106 | return [ 107 | child.dataset.flipId!, 108 | { 109 | rect: childBCR, 110 | opacity: parseFloat(window.getComputedStyle(child).opacity || '1'), 111 | domDataForExitAnimations 112 | } 113 | ] 114 | }) as [string, FlippedElementPositionDatumBeforeUpdate][] 115 | 116 | const flippedElementPositions = flippedElementPositionsTupleArray.reduce( 117 | addTupleToObject, 118 | {} 119 | ) 120 | 121 | // do this at the very end since we want to cache positions of elements 122 | // while they are mid-transition 123 | cancelInProgressAnimations( 124 | inProgressAnimations, 125 | flippedElements.concat(inverseFlippedElements) 126 | ) 127 | 128 | return { 129 | flippedElementPositions, 130 | cachedOrderedFlipIds: filteredFlippedElements.map( 131 | ([el]) => el.dataset.flipId! 132 | ) 133 | } 134 | } 135 | 136 | export default getFlippedElementPositionsBeforeUpdate 137 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate/types.ts: -------------------------------------------------------------------------------- 1 | import { BoundingClientRect, BaseFlippedElementPositions } from '../types' 2 | import { InProgressAnimations, FlipCallbacks, FlipId } from '../../../types' 3 | 4 | export interface DomDataForExitAnimations { 5 | element: HTMLElement 6 | parent: HTMLElement 7 | childPosition: BoundingClientRect 8 | } 9 | 10 | export interface FlippedElementPositionDatumBeforeUpdate 11 | extends BaseFlippedElementPositions { 12 | domDataForExitAnimations: DomDataForExitAnimations 13 | } 14 | 15 | export interface FlippedElementPositionsBeforeUpdate { 16 | [key: string]: FlippedElementPositionDatumBeforeUpdate 17 | } 18 | 19 | export type CachedOrderedFlipIds = string[] 20 | 21 | export interface FlippedElementPositionsBeforeUpdateReturnVals { 22 | flippedElementPositions: FlippedElementPositionsBeforeUpdate 23 | cachedOrderedFlipIds: CachedOrderedFlipIds 24 | } 25 | 26 | export interface GetFlippedElementPositionsBeforeUpdateArgs { 27 | element: HTMLElement 28 | flipCallbacks: FlipCallbacks 29 | inProgressAnimations: InProgressAnimations 30 | portalKey?: string 31 | } 32 | 33 | export type ParentBCRs = Array<[HTMLElement, BoundingClientRect]> 34 | 35 | export type ChildIdsToParentBCRs = Record 36 | export type ChildIdsToParents = Record 37 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/getFlippedElementPositions/types.ts: -------------------------------------------------------------------------------- 1 | export interface BoundingClientRect { 2 | top: number 3 | left: number 4 | width: number 5 | height: number 6 | bottom: number 7 | right: number 8 | } 9 | 10 | export interface BaseFlippedElementPositions { 11 | rect: BoundingClientRect 12 | opacity: number 13 | } 14 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/getFlippedElementPositions/utilities.ts: -------------------------------------------------------------------------------- 1 | import { toArray, assign } from '../../utilities' 2 | import * as constants from '../../constants' 3 | import { BoundingClientRect } from './types' 4 | 5 | export const addTupleToObject = ( 6 | acc: Record, 7 | curr: [string, T] 8 | ): Record => assign(acc, { [curr[0]]: curr[1] }) 9 | 10 | export const getAllElements = ( 11 | element?: HTMLElement, 12 | portalKey?: string 13 | ): HTMLElement[] => { 14 | if (portalKey) { 15 | return toArray( 16 | document.querySelectorAll(`[${constants.DATA_PORTAL_KEY}="${portalKey}"]`) 17 | ) 18 | } else { 19 | return toArray(element!.querySelectorAll(`[${constants.DATA_FLIP_ID}]`)) 20 | } 21 | } 22 | export const getRects = ( 23 | flippedElements: HTMLElement[] 24 | ): [HTMLElement, BoundingClientRect][] => { 25 | return flippedElements.map( 26 | (child: HTMLElement): [HTMLElement, BoundingClientRect] => [ 27 | child, 28 | child.getBoundingClientRect() 29 | ] 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/flip/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InProgressAnimations, 3 | FlipCallbacks, 4 | StaggerConfig, 5 | HandleEnterUpdateDelete, 6 | OnFlipperComplete, 7 | OnFlipperStart, 8 | DecisionData 9 | } from '../types' 10 | import { FlippedElementPositionsBeforeUpdate } from './getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate/types' 11 | import { FlippedElementPositionsAfterUpdate } from './getFlippedElementPositions/getFlippedElementPositionsAfterUpdate/types' 12 | import { CachedOrderedFlipIds } from './getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate/types' 13 | import { SpringOption } from '../springSettings/types' 14 | 15 | export type FlippedIds = string[] 16 | 17 | export type GetElement = (id: string) => HTMLElement 18 | 19 | export interface BaseFlipArgs { 20 | flipCallbacks: FlipCallbacks 21 | getElement: GetElement 22 | flippedElementPositionsBeforeUpdate: FlippedElementPositionsBeforeUpdate 23 | flippedElementPositionsAfterUpdate: FlippedElementPositionsAfterUpdate 24 | inProgressAnimations: InProgressAnimations 25 | decisionData?: DecisionData 26 | } 27 | 28 | export interface OnFlipKeyUpdateArgs { 29 | cachedOrderedFlipIds: CachedOrderedFlipIds 30 | inProgressAnimations: InProgressAnimations 31 | flippedElementPositionsBeforeUpdate: FlippedElementPositionsBeforeUpdate 32 | flipCallbacks: FlipCallbacks 33 | containerEl: HTMLElement 34 | applyTransformOrigin?: boolean 35 | spring?: SpringOption 36 | debug?: boolean 37 | portalKey?: string 38 | staggerConfig?: StaggerConfig 39 | handleEnterUpdateDelete?: HandleEnterUpdateDelete 40 | onComplete?: OnFlipperComplete 41 | onStart?: OnFlipperStart 42 | decisionData: DecisionData 43 | } 44 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/forked-rebound/Loopers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * 10 | */ 11 | 12 | import { onFrame, performanceNow } from "./util" 13 | 14 | /** 15 | * Plays each frame of the SpringSystem on animation 16 | * timing loop. This is the default type of looper for a new spring system 17 | * as it is the most common when developing UI. 18 | * @public 19 | */ 20 | export class AnimationLooper { 21 | run() { 22 | onFrame(() => { 23 | this.springSystem.loop(performanceNow()) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/forked-rebound/README.md: -------------------------------------------------------------------------------- 1 | This version of [rebound-js](https://github.com/facebook/rebound-js) has been forked to reduce payload size because the current version of the library doesn't offer an easy way to import only what you need. 2 | 3 | Rebound is used instead of React-Flip-Toolkit's previous spring library, WobbleJS, because rebound's unified render loop results in significantly increased performance, especially when there are more than a few animated elements. 4 | 5 | I stripped the flow types because I was having trouble getting it to work with the parcel-built dom tests for some reason. That might have been a bad idea. 6 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/forked-rebound/SpringConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * 10 | */ 11 | 12 | /** 13 | * Maintains a set of tension and friction constants 14 | * for a Spring. You can use fromOrigamiTensionAndFriction to convert 15 | * values from the [Origami](http://facebook.github.io/origami/) 16 | * design tool directly to Rebound spring constants. 17 | * @public 18 | */ 19 | class SpringConfig { 20 | constructor(tension, friction) { 21 | this.tension = tension 22 | this.friction = friction 23 | } 24 | } 25 | 26 | export default SpringConfig 27 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/forked-rebound/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * 10 | */ 11 | 12 | export { default as SpringSystem } from "./SpringSystem" 13 | export { onFrame } from "./util" 14 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/forked-rebound/onFrame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * 10 | */ 11 | 12 | let _onFrame 13 | if (typeof window !== "undefined") { 14 | _onFrame = window.requestAnimationFrame 15 | } 16 | 17 | _onFrame = 18 | _onFrame || 19 | function(callback) { 20 | window.setTimeout(callback, 1000 / 60) 21 | } 22 | 23 | export default _onFrame 24 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/forked-rebound/types.d.ts: -------------------------------------------------------------------------------- 1 | type SpringCallback = (spring: Spring) => void 2 | 3 | export interface AddListenerArgs { 4 | onSpringActivate?: SpringCallback 5 | onSpringUpdate: SpringCallback 6 | onSpringAtRest: SpringCallback 7 | } 8 | 9 | export interface Spring { 10 | setOvershootClampingEnabled: (overshootClamping: boolean) => void 11 | addListener: (addListenerArgs: AddListenerArgs) => void 12 | destroy: () => void 13 | setEndValue: (endValue: number) => void 14 | getCurrentValue: () => number 15 | } 16 | 17 | export interface SpringSystemInterface { 18 | createSpring: (stiffness: number, damping: number) => Spring 19 | } 20 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/forked-rebound/types.ts: -------------------------------------------------------------------------------- 1 | type SpringCallback = (spring: Spring) => void 2 | 3 | export interface AddListenerArgs { 4 | onSpringActivate?: SpringCallback 5 | onSpringUpdate: SpringCallback 6 | onSpringAtRest: SpringCallback 7 | } 8 | 9 | export interface Spring { 10 | setOvershootClampingEnabled: (overshootClamping: boolean) => void 11 | addListener: (addListenerArgs: AddListenerArgs) => void 12 | destroy: () => void 13 | setEndValue: (endValue: number) => void 14 | getCurrentValue: () => number 15 | } 16 | 17 | export interface SpringSystemInterface { 18 | createSpring: (stiffness: number, damping: number) => Spring 19 | } 20 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/forked-rebound/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * 10 | */ 11 | 12 | export { default as onFrame } from './onFrame' 13 | 14 | const start = Date.now() 15 | export const performanceNow = 16 | typeof performance === 'object' && typeof performance.now === 'function' 17 | ? () => performance.now() 18 | : () => Date.now() - start 19 | 20 | // Lop off the first occurence of the reference in the Array. 21 | export function removeFirst(array, item) { 22 | const idx = array.indexOf(item) 23 | idx !== -1 && array.splice(idx, 1) 24 | } 25 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as utilities from './utilities' 2 | import * as constants from './constants' 3 | export { default as Flipper } from './Flipper' 4 | export { default as getFlippedElementPositionsBeforeUpdate } from './flip/getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate' 5 | export * from './flip' 6 | export { utilities, constants } 7 | export { default as spring } from './Spring' 8 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/springSettings/index.test.ts: -------------------------------------------------------------------------------- 1 | import { getSpringConfig, springPresets } from './index' 2 | 3 | describe('getSpringConfig', () => { 4 | it('should default to noWobble settings if nothing is provided', () => { 5 | expect(getSpringConfig()).toEqual(springPresets.noWobble) 6 | }) 7 | 8 | it('should allow the use of a valid spring referring to a preset ', () => { 9 | expect( 10 | getSpringConfig({ 11 | flipperSpring: 'wobbly', 12 | flippedSpring: undefined 13 | }) 14 | ).toEqual(springPresets.wobbly) 15 | 16 | expect( 17 | getSpringConfig({ 18 | flippedSpring: undefined, 19 | flipperSpring: 'stiff' 20 | }) 21 | ).toEqual(springPresets.stiff) 22 | }) 23 | 24 | it('should allow the passing in of an object, and use noWobble to fill in missing keys', () => { 25 | expect( 26 | getSpringConfig({ 27 | flippedSpring: undefined, 28 | flipperSpring: { stiffness: 400 } 29 | }) 30 | ).toEqual({ 31 | stiffness: 400, 32 | damping: 26 33 | }) 34 | }) 35 | 36 | it('should always override what is provided in flippedSpring with the data in flipperSpring if it exists', () => { 37 | expect( 38 | getSpringConfig({ 39 | flipperSpring: { stiffness: 400, damping: 700 }, 40 | flippedSpring: 'stiff' 41 | }) 42 | ).toEqual(springPresets.stiff) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/springSettings/index.ts: -------------------------------------------------------------------------------- 1 | import { isObject, assign } from '../utilities' 2 | import { SpringPresets, SpringConfig, SpringOption } from './types' 3 | 4 | // adapted from 5 | // https://github.com/chenglou/react-motion/blob/master/src/presets.js 6 | export const springPresets: SpringPresets = { 7 | noWobble: { stiffness: 200, damping: 26 }, 8 | gentle: { stiffness: 120, damping: 14 }, 9 | veryGentle: { stiffness: 130, damping: 17 }, 10 | wobbly: { stiffness: 180, damping: 12 }, 11 | stiff: { stiffness: 260, damping: 26 } 12 | } 13 | 14 | function argIsSpringConfig( 15 | arg: SpringConfig | keyof SpringPresets | undefined 16 | ): arg is SpringConfig { 17 | return isObject(arg) 18 | } 19 | 20 | export const normalizeSpring = ( 21 | spring?: SpringConfig | keyof SpringPresets | any 22 | ) => { 23 | if (argIsSpringConfig(spring)) { 24 | return spring 25 | } else if (Object.keys(springPresets).indexOf(spring) > -1) { 26 | return springPresets[spring] 27 | } else { 28 | return {} 29 | } 30 | } 31 | 32 | export const getSpringConfig = ({ 33 | flipperSpring, 34 | flippedSpring 35 | }: { flipperSpring?: SpringOption; flippedSpring?: SpringOption } = {}) => { 36 | return assign( 37 | {}, 38 | springPresets.noWobble, 39 | normalizeSpring(flipperSpring), 40 | normalizeSpring(flippedSpring) 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/springSettings/types.ts: -------------------------------------------------------------------------------- 1 | import { IndexableObject } from '../utilities/types' 2 | 3 | export interface SpringConfig { 4 | stiffness?: number 5 | damping?: number 6 | overshootClamping?: boolean 7 | } 8 | 9 | export interface SpringPresets extends IndexableObject { 10 | noWobble: SpringConfig 11 | gentle: SpringConfig 12 | veryGentle: SpringConfig 13 | wobbly: SpringConfig 14 | stiff: SpringConfig 15 | } 16 | 17 | export type SpringOption = SpringConfig | keyof SpringPresets 18 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | import { IndexableObject } from './types' 2 | 3 | export const isNumber = (x: any) => typeof x === 'number' 4 | 5 | export const isFunction = (x: any) => typeof x === 'function' 6 | 7 | export const isObject = (x: any) => 8 | Object.prototype.toString.call(x) === '[object Object]' 9 | 10 | export const toArray = (arrayLike: ArrayLike) => 11 | Array.prototype.slice.apply(arrayLike) 12 | 13 | export const getDuplicateValsAsStrings = (arr: string[]): string[] => { 14 | const baseObj: IndexableObject = {} 15 | const obj = arr.reduce((acc, curr) => { 16 | acc[curr] = (acc[curr] || 0) + 1 17 | return acc 18 | }, baseObj) 19 | return Object.keys(obj).filter(val => obj[val] > 1) 20 | } 21 | 22 | // tslint only likes this with a regular function, not an arrow function 23 | export function assign(target: IndexableObject, ...args: IndexableObject[]) { 24 | args.forEach(arg => { 25 | if (!arg) { 26 | return 27 | } 28 | // Skip over if undefined or null 29 | for (const nextKey in arg) { 30 | // Avoid bugs when hasOwnProperty is shadowed 31 | if (Object.prototype.hasOwnProperty.call(arg, nextKey)) { 32 | target[nextKey] = arg[nextKey] 33 | } 34 | } 35 | }) 36 | return target 37 | } 38 | 39 | export const tweenProp = (start: number, end: number, position: number) => 40 | start + (end - start) * position 41 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/utilities/tweenProp.test.ts: -------------------------------------------------------------------------------- 1 | import { tweenProp } from './' 2 | 3 | describe('tweenProp', () => { 4 | it('interpolates a number from 0 - 1 to a given range ', () => { 5 | expect(tweenProp(5, 15, 0)).toBe(5) 6 | expect(tweenProp(5, 15, 1)).toBe(15) 7 | expect(tweenProp(5, 15, 0.5)).toBe(10) 8 | expect(tweenProp(5, 15, 0.75)).toBe(12.5) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /packages/flip-toolkit/src/utilities/types.ts: -------------------------------------------------------------------------------- 1 | export type IndexableObject = Record 2 | -------------------------------------------------------------------------------- /packages/flip-toolkit/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vanilla Test 7 | 8 | 9 | 10 | 63 | 64 | 65 | 66 |
67 |
68 |
hi there
69 |
70 |
71 | 72 |
    73 |
  • 74 |
    75 |
    76 |
    77 |
    78 |
    79 |
  • 80 |
  • 81 |
    82 |
    83 |
    84 |
    85 |
    86 |
  • 87 |
  • 88 |
    89 |
    90 |
    91 |
    92 |
    93 |
  • 94 |
  • 95 |
    96 |
    97 |
    98 |
    99 |
    100 |
  • 101 |
102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /packages/flip-toolkit/test/index.js: -------------------------------------------------------------------------------- 1 | import { Flipper } from '../src/index.ts' 2 | 3 | const container = document.querySelector('.container') 4 | const box = document.querySelector('.box') 5 | const innerBox = document.querySelector('.innerBox') 6 | 7 | const flipper = new Flipper({ element: container }) 8 | 9 | box.addEventListener('click', () => { 10 | flipper.recordBeforeUpdate() 11 | box.classList.toggle('big-box') 12 | flipper.update() 13 | }) 14 | 15 | flipper.addFlipped({ 16 | element: box, 17 | flipId: 'box', 18 | inverted: innerBox, 19 | shouldFlip: () => { 20 | console.log('shouldFlip called') 21 | return true 22 | }, 23 | shouldInvert: () => { 24 | console.log('shouldInvert called') 25 | return true 26 | } 27 | }) 28 | 29 | flipper.addInverted({ 30 | element: innerBox, 31 | parent: box 32 | }) 33 | 34 | const container2 = document.querySelector('.container-2') 35 | const flipper2 = new Flipper({ 36 | element: container2, 37 | spring: { stiffness: 4, damping: 4 }, 38 | }) 39 | 40 | container2.addEventListener('click', () => { 41 | flipper2.recordBeforeUpdate() 42 | container2.classList.toggle('reversed') 43 | flipper2.update() 44 | }) 45 | 46 | Array.from(container2.querySelectorAll('li')).forEach((li, i) => { 47 | flipper2.addFlipped({ 48 | element: li, 49 | flipId: `li-${i}`, 50 | stagger: true 51 | }) 52 | }) 53 | 54 | Array.from(container2.querySelectorAll('.container-2 .inner')).forEach( 55 | (div, i) => { 56 | flipper2.addFlipped({ 57 | element: div, 58 | flipId: `inner-${i}`, 59 | delayUntil: `li-${i}`, 60 | }) 61 | } 62 | ) 63 | 64 | Array.from(container2.querySelectorAll('.container-2 .inverted')).forEach( 65 | (div, i) => { 66 | flipper2.addInverted({ 67 | element: div, 68 | parent: div.parentNode, 69 | }) 70 | } 71 | ) 72 | -------------------------------------------------------------------------------- /packages/flip-toolkit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": ["es6", "dom", "es2016", "es2017"], 6 | "sourceMap": true, 7 | "allowJs": true, 8 | "jsx": "react", 9 | "moduleResolution": "node", 10 | "forceConsistentCasingInFileNames": true, 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": true, 13 | "allowSyntheticDefaultImports": true, 14 | "skipLibCheck": true, 15 | "esModuleInterop": true, 16 | "strict": true, 17 | "resolveJsonModule": true, 18 | "isolatedModules": true 19 | }, 20 | "include": ["src", "@types"], 21 | "exclude": [ 22 | "node_modules", 23 | "build", 24 | "dist", 25 | "example", 26 | "src/**/*.test.ts", 27 | "src/**/*.test.tsx", 28 | "__tests__" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/.babelrc: -------------------------------------------------------------------------------- 1 | // just for tests + parcel demo 2 | { 3 | "presets": [ 4 | "@babel/preset-typescript", 5 | "@babel/preset-react", 6 | "@babel/preset-env" 7 | ], 8 | "plugins": ["@babel/plugin-proposal-class-properties"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/CardsExample/Card.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Flipped, spring } from '../../src' 3 | 4 | class Card extends PureComponent { 5 | hideElements = (el, { previous: prev, current: curr }) => { 6 | if (prev !== this.props.i) return 7 | const elements = [].slice.apply(el.querySelectorAll('*[data-fade-in]')) 8 | elements.forEach(el => (el.style.opacity = '0')) 9 | el.style.zIndex = 20 10 | } 11 | animateIn = (el, { previous: prev, current: curr }) => { 12 | if (prev !== this.props.i) return 13 | el.style.zIndex = '' 14 | const elements = [...el.querySelectorAll('*[data-fade-in]')] 15 | elements.forEach((el, i) => { 16 | spring({ 17 | values: { 18 | translateY: [-30, 0], 19 | opacity: [0, 1] 20 | }, 21 | onUpdate: ({ translateY, opacity }) => { 22 | el.style.opacity = opacity 23 | el.style.transform = `translateY(${translateY}px)` 24 | }, 25 | delay: i * 75 26 | }) 27 | }) 28 | } 29 | 30 | shouldFlip = (prev, current) => { 31 | if (prev === this.props.i) return true 32 | return false 33 | } 34 | render() { 35 | const { parentFlipId, d, i, setFocusedIndex } = this.props 36 | return ( 37 |
  • 38 | 44 |
    setFocusedIndex(i)} 47 | role="button" 48 | > 49 | 50 |
    51 |

    52 | {d.name} 53 |

    54 | 58 | {`user 63 | 64 |

    65 | {d.job} 66 |

    67 | 68 | 72 |
    76 | 77 |
    78 |
    79 |
    80 |
    81 |
  • 82 | ) 83 | } 84 | } 85 | 86 | export default Card 87 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/CardsExample/FocusedUser.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Flipped, spring } from '../../src' 3 | 4 | class FocusedUser extends Component { 5 | hideElements = el => { 6 | const elements = [].slice.apply(el.querySelectorAll('*[data-fade-in]')) 7 | elements.forEach(el => (el.style.opacity = '0')) 8 | } 9 | animateIn = el => { 10 | const elements = [...el.querySelectorAll('*[data-fade-in]')] 11 | elements.forEach((el, i) => { 12 | spring({ 13 | values: { 14 | translateY: [-30, 0], 15 | opacity: [0, 1] 16 | }, 17 | onUpdate: ({ translateY, opacity }) => { 18 | el.style.opacity = opacity 19 | el.style.transform = `translateY(${translateY}px)` 20 | }, 21 | delay: i * 75 22 | }) 23 | }) 24 | } 25 | 26 | render() { 27 | const { data, index, close } = this.props 28 | const parentFlipId = `card-${index}` 29 | 30 | if (typeof index !== 'number') return null 31 | 32 | return ( 33 |
    34 | 39 |
    (this.el = el)} 43 | > 44 | 45 |
    46 |
    47 | 54 |

    55 | {data.name} 56 |

    57 | 58 | {`user 63 | 64 | 65 |

    66 | {data.job} 67 |

    68 |

    69 | {data.text} 70 |

    71 | 72 |
    76 | 77 |
    78 |
    79 | 80 |
    81 |
    82 |
    83 | ) 84 | } 85 | } 86 | 87 | export default FocusedUser 88 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/CardsExample/UserGrid.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Card from './Card' 3 | import { spring } from '../../src' 4 | 5 | class UserGrid extends Component { 6 | hideElements = (el, startId) => { 7 | if (startId !== 'focusedUser') return 8 | const elements = [].slice.apply(el.querySelectorAll('*[data-fade-in]')) 9 | elements.forEach(el => (el.style.opacity = '0')) 10 | el.style.zIndex = 2 11 | } 12 | animateIn = (el, startId) => { 13 | if (startId !== 'focusedUser') return 14 | 15 | ;[...el.querySelectorAll('*[data-fade-in]')].forEach((el, i) => { 16 | spring({ 17 | values: { 18 | translateY: [-30, 0], 19 | opacity: [0, 1] 20 | }, 21 | onUpdate: ({ translateY, opacity }) => { 22 | el.style.opacity = opacity 23 | el.style.transform = `translateY(${translateY}px)` 24 | }, 25 | delay: i * 75 26 | }) 27 | }) 28 | 29 | el.style.zIndex = 1 30 | } 31 | render() { 32 | return ( 33 |
      34 | {this.props.data.map((d, i) => { 35 | const parentFlipId = `card-${i}` 36 | if (i === this.props.focusedIndex) return null 37 | return ( 38 | 44 | ) 45 | })} 46 |
    47 | ) 48 | } 49 | } 50 | 51 | export default UserGrid 52 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/CardsExample/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Flipper } from '../../src' 3 | import userData from './userData.json' 4 | import UserGrid from './UserGrid' 5 | import FocusedUser from './FocusedUser' 6 | import './styles.css' 7 | 8 | export default class CardsExample extends Component { 9 | state = { 10 | focusedIndex: undefined, 11 | speed: 'normal', 12 | userData: userData 13 | } 14 | setFocusedIndex = index => { 15 | this.setState({ 16 | focusedIndex: index 17 | }) 18 | } 19 | 20 | render() { 21 | return ( 22 | 28 |
    (this.el = el)}> 29 |

    Avatar cards

    30 |

    31 | An example made somewhat needlessly complicated in order to show off 32 | some advanced features: 33 |

    34 |
      35 |
    • 36 | The non-active cards move towards their new positions in the grid 37 | when a card is clicked 38 |
    • 39 |
    • There are multiple nested transitions in the card
    • 40 |
    • 41 | The card's background opacity is animated in addition to the 42 | position 43 |
    • 44 |
    45 |

    Slow the animation down to better follow all the transitions:

    46 |
    47 | Speed: 48 | 61 | 74 |
    75 |
    76 | 81 | { 84 | this.setState({ focusedIndex: null }) 85 | }} 86 | data={ 87 | typeof this.state.focusedIndex === 'number' 88 | ? userData[this.state.focusedIndex] 89 | : null 90 | } 91 | /> 92 |
    93 | ) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/CardsExample/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #4f3be5; 3 | --text-color: #19122c; 4 | --grey: #f6f6f8; 5 | } 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | body { 12 | font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", 13 | "Roboto", "Helvetica Neue", Arial, sans-serif; 14 | color: var(--text-color); 15 | } 16 | 17 | .cardsExample label + label { 18 | margin-left: 1rem; 19 | } 20 | .cardsExample label:first-of-type { 21 | margin-left: 1rem; 22 | } 23 | 24 | .cardGrid { 25 | display: grid; 26 | list-style: none; 27 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 28 | grid-auto-rows: 180px; 29 | grid-gap: 2rem; 30 | background-color: var(--grey); 31 | padding: 2rem; 32 | min-height: 100vh; 33 | } 34 | 35 | .header { 36 | padding: 0 0 0 2rem; 37 | } 38 | 39 | .header h1 { 40 | font-size: 1.6rem; 41 | } 42 | 43 | .gridItem { 44 | background-color: white; 45 | text-align: center; 46 | margin: 0; 47 | cursor: pointer; 48 | height: 100%; 49 | overflow: hidden; 50 | position: relative; 51 | border-radius: 3px; 52 | box-shadow: 0 0 18px #0000000d; 53 | transition: box-shadow 0.3s; 54 | } 55 | 56 | .gridItem:hover { 57 | box-shadow: 0 0 35px #00000023; 58 | } 59 | 60 | .gridItem > div { 61 | width: 100%; 62 | height: 100%; 63 | } 64 | 65 | .gridItem > div:first-of-type { 66 | padding: 1rem; 67 | z-index: 1; 68 | position: relative; 69 | } 70 | 71 | .gridItemTitle { 72 | font-size: 1.2rem; 73 | } 74 | 75 | .gridItemAvatar { 76 | border-radius: 100px; 77 | width: 3rem; 78 | height: 3rem; 79 | margin: 0 auto; 80 | } 81 | 82 | .gridItemBackground { 83 | position: absolute; 84 | height: 200%; 85 | top: 50%; 86 | bottom: 0; 87 | left: -100%; 88 | width: 300%; 89 | z-index: -1; 90 | background-color: var(--primary-color); 91 | opacity: 0.3; 92 | } 93 | 94 | .backgroundContainer { 95 | position: absolute; 96 | top: 0; 97 | left: 0; 98 | width: 100%; 99 | height: 100%; 100 | } 101 | 102 | .gridItemFocused { 103 | width: 100vw; 104 | height: 100vh; 105 | z-index: 3; 106 | position: relative; 107 | } 108 | 109 | .gridItemFocused > div { 110 | display: flex; 111 | justify-content: center; 112 | align-items: center; 113 | } 114 | 115 | .gridItemFocusedClose { 116 | position: absolute; 117 | padding: 1rem 2rem; 118 | top: 1rem; 119 | right: 1rem; 120 | cursor: pointer; 121 | background-color: transparent; 122 | font-size: 3rem; 123 | border: none; 124 | color: white; 125 | } 126 | 127 | .gridItemFocusedClose:hover { 128 | background-color: rgba(0, 0, 0, 0.1); 129 | } 130 | 131 | .focusedItemBackground { 132 | position: fixed; 133 | top: 0; 134 | left: 0; 135 | right: 0; 136 | bottom: 0; 137 | z-index: 2; 138 | display: flex; 139 | justify-content: center; 140 | align-items: center; 141 | } 142 | 143 | .gridItemJob { 144 | font-size: 1rem; 145 | font-weight: normal; 146 | } 147 | 148 | .gridItemDescription { 149 | max-width: 40rem; 150 | margin: 0 auto; 151 | color: white; 152 | line-height: 1.6; 153 | font-size: 1.2rem; 154 | margin-top: 2rem; 155 | } 156 | 157 | .gridItemFocused .gridItemBackground { 158 | top: 0%; 159 | opacity: 1; 160 | } 161 | 162 | .gridItemFocused .gridItemTitle { 163 | font-size: 3.2rem; 164 | color: white; 165 | margin-top: 0; 166 | } 167 | 168 | .gridItemFocused .gridItemAvatar { 169 | width: 12rem; 170 | height: 12rem; 171 | background: #5c657b; 172 | } 173 | 174 | .gridItemFocused .gridItemJob { 175 | font-size: 1.8rem; 176 | color: white; 177 | } 178 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/FlipMove/Card.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Flipped, spring } from '../../src' 3 | 4 | const onElementAppear = (el, index) => 5 | spring({ 6 | onUpdate: val => { 7 | el.style.opacity = val 8 | }, 9 | delay: index * 50 10 | }) 11 | 12 | const onExit = type => (el, index, removeElement) => { 13 | spring({ 14 | config: { overshootClamping: true }, 15 | onUpdate: val => { 16 | el.style.transform = `scale${type === 'grid' ? 'X' : 'Y'}(${1 - val})` 17 | }, 18 | delay: index * 50, 19 | onComplete: removeElement 20 | }) 21 | 22 | return () => { 23 | el.style.opacity = '' 24 | removeElement() 25 | } 26 | } 27 | 28 | const onGridExit = onExit('grid') 29 | const onListExit = onExit('list') 30 | 31 | class Card extends PureComponent { 32 | shouldFlip = (prev, current) => { 33 | if (prev.type !== current.type) { 34 | return true 35 | } 36 | return false 37 | } 38 | render() { 39 | const { id, title, type, stagger, addToFilteredIds } = this.props 40 | const flipId = `item-${id}` 41 | return ( 42 | 51 |
  • 52 | 53 |
    54 | 60 |
    61 |

    {title}

    62 |

    {title}

    63 |
    64 |
    65 | 66 | 71 | 77 | 78 |
    79 |
    80 |
  • 81 |
    82 | ) 83 | } 84 | } 85 | 86 | export default Card 87 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/FlipMove/index.css: -------------------------------------------------------------------------------- 1 | .fm-example { 2 | padding: 0 2rem; 3 | min-height: 150vh; 4 | } 5 | 6 | .fm-description { 7 | margin-bottom: 1rem; 8 | } 9 | 10 | .fm-description h1 { 11 | margin-bottom: 1rem; 12 | } 13 | 14 | .fm-example > * { 15 | max-width: 1100px; 16 | margin-left: auto; 17 | margin-right: auto; 18 | } 19 | 20 | .fm-grid > ul { 21 | list-style-type: none; 22 | display: grid; 23 | grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); 24 | grid-auto-rows: 8rem; 25 | grid-gap: 1rem; 26 | padding: 0; 27 | margin: 0; 28 | } 29 | 30 | .fm-list { 31 | grid-gap: 1rem; 32 | margin: 2rem 0; 33 | } 34 | 35 | .fm-grid, 36 | .fm-list { 37 | border-radius: 5px; 38 | background-color: #ececec; 39 | margin-top: 2.5rem; 40 | max-width: 900px; 41 | } 42 | 43 | .fm-list > ul, 44 | .fm-grid > ul { 45 | width: 100%; 46 | list-style-type: none; 47 | margin: 0; 48 | padding: 1rem; 49 | } 50 | 51 | .fm-list .fm-item { 52 | margin-bottom: 1rem; 53 | } 54 | 55 | .fm-list .fm-item:last-of-type { 56 | margin-bottom: 0; 57 | } 58 | 59 | .fm-item { 60 | overflow: hidden; 61 | display: block; 62 | border-radius: 4px; 63 | background-color: white; 64 | } 65 | 66 | .fm-item h3 { 67 | font-size: 1.1rem; 68 | } 69 | 70 | .fm-item > div { 71 | padding: 0 1.25rem; 72 | display: flex; 73 | justify-content: space-between; 74 | align-items: flex-start; 75 | } 76 | 77 | .fm-item h3 { 78 | margin-bottom: 0; 79 | } 80 | 81 | .fm-item p { 82 | font-style: italic; 83 | margin-top: 0.5rem; 84 | } 85 | 86 | .fm-remove { 87 | border: 0; 88 | background: transparent; 89 | cursor: pointer; 90 | font-size: 2.2rem; 91 | color: #ff4057; 92 | position: relative; 93 | left: 0.5rem; 94 | width: 1.5rem; 95 | } 96 | 97 | .fm-show-all { 98 | padding: 0.5rem 1rem; 99 | background-color: #ff4057; 100 | color: white; 101 | font-weight: bold; 102 | border: 0; 103 | border-radius: 3px; 104 | } 105 | 106 | .fm-flex-container { 107 | display: flex; 108 | flex-wrap: wrap; 109 | } 110 | 111 | .fm-flex-container > label { 112 | min-width: 4.5rem; 113 | } 114 | 115 | legend { 116 | font-weight: bold; 117 | } 118 | 119 | .fm-example fieldset { 120 | border: 0; 121 | padding: 1rem; 122 | } 123 | 124 | .fm-example label { 125 | margin-right: 1.5rem; 126 | cursor: pointer; 127 | } 128 | 129 | .fm-example input { 130 | position: relative; 131 | margin-right: 0.5rem; 132 | } 133 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GestureSidebarExample/assets/nighttime.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GestureSidebarExample/assets/nighttime.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GestureSidebarExample/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 2 | /* eslint-disable no-return-assign */ 3 | 4 | import React, { Component } from 'react' 5 | import { Flipper, Flipped, Swipe } from '../../src' 6 | import backgroundImg from './assets/nighttime.jpg' 7 | import './styles.css' 8 | 9 | class PaymentSidebar extends Component { 10 | state = { collapsed: true } 11 | toggleCollapsed = () => { 12 | this.setState({ collapsed: !this.state.collapsed }) 13 | } 14 | render() { 15 | const { collapsed } = this.state 16 | const sidebarClassName = `sidebar ${collapsed ? 'sidebarCollapsed' : ''}` 17 | 18 | const swipeConfig = { 19 | initFlip: this.toggleCollapsed, 20 | cancelFlip: this.toggleCollapsed 21 | } 22 | 23 | return ( 24 | 25 | 26 | 27 |
    28 | 29 |
    30 | 31 |
    35 | 36 | 37 |
    38 | 39 |

    40 | Lorem ipsum dolor sit amet consectetur 41 |

    42 |
    43 |
    44 | Lorem ipsum dolor, sit amet consectetur adipisicing elit. 45 | Molestiae deleniti reprehenderit est necessitatibus qui 46 | iste maiores enim amet atque nostrum? Facere ad eveniet 47 | cupiditate molestiae, repellendus nisi consectetur quasi 48 | adipisci. 49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 | 56 | 57 | ) 58 | } 59 | } 60 | 61 | export default PaymentSidebar 62 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GestureSidebarExample/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --sidebar-primary: black; 3 | } 4 | 5 | .sidebar { 6 | color: white; 7 | position: relative; 8 | z-index: 1; 9 | height: 100vh; 10 | width: 50rem; 11 | overflow: hidden; 12 | min-height: 50rem; 13 | background-color: var(--sidebar-primary); 14 | cursor: pointer; 15 | } 16 | 17 | .sidebar.sidebarCollapsed { 18 | min-height: auto; 19 | } 20 | 21 | .sidebarHeader { 22 | font-size: 2rem; 23 | line-height: 1.5; 24 | font-weight: bold; 25 | color: #fff; 26 | transform-origin: 0 0; 27 | display: inline-block; 28 | width: 100%; 29 | margin-bottom: 0.5rem; 30 | user-select: none; 31 | } 32 | 33 | .sidebarCollapsedSubheader { 34 | display: none; 35 | opacity: 0; 36 | position: absolute; 37 | left: 34%; 38 | top: 3rem; 39 | font-size: 1.1rem; 40 | } 41 | 42 | .sidebarBody { 43 | padding-left: 1.75rem; 44 | padding-right: 1.75rem; 45 | padding-bottom: 1.75rem; 46 | padding-top: 10vh; 47 | z-index: 2; 48 | position: relative; 49 | } 50 | 51 | .decorativeImg { 52 | height: 18rem; 53 | width: 100%; 54 | background-repeat: no-repeat; 55 | background-size: cover; 56 | background-position: 50% 50%; 57 | position: absolute; 58 | z-index: 0; 59 | transform-origin: 0 0; 60 | } 61 | 62 | .decorativeImg::after { 63 | content: ""; 64 | position: absolute; 65 | top: 0; 66 | left: 0; 67 | right: 0; 68 | bottom: 0; 69 | z-index: 1; 70 | background: linear-gradient( transparent, hsla(0, 0%, 0%, 1)); 71 | } 72 | 73 | .sidebarBtn { 74 | margin-top: 1rem; 75 | } 76 | 77 | @keyframes fadeIn { 78 | 0% { 79 | opacity: 0; 80 | } 81 | 100% { 82 | opacity: 1; 83 | } 84 | } 85 | 86 | .openButton { 87 | position: absolute; 88 | right: 1rem; 89 | top: 50%; 90 | transform: translateY(-50%); 91 | animation: fadeIn 0.5s forwards; 92 | animation-delay: 400ms; 93 | background: transparent; 94 | border: 0; 95 | opacity: 0; 96 | } 97 | 98 | .toggleOpenIcon { 99 | color: #fff; 100 | width: 1.2rem; 101 | height: 1.2rem; 102 | } 103 | 104 | .sidebarCollapsed { 105 | height: 5rem; 106 | } 107 | 108 | .sidebarCollapsed .decorativeImg { 109 | width: 28%; 110 | height: 100%; 111 | } 112 | 113 | .sidebarCollapsed::after { 114 | opacity: 0; 115 | } 116 | 117 | .sidebarCollapsed .sidebarBody { 118 | padding-top: 0; 119 | } 120 | 121 | .sidebarCollapsed .sidebarHeader { 122 | position: relative; 123 | top: 1.1rem; 124 | left: 34%; 125 | font-size: 1rem; 126 | overflow: hidden; 127 | white-space: nowrap; 128 | max-width: calc(66% - 3rem); 129 | text-overflow: ellipsis; 130 | } 131 | 132 | .sidebarCollapsed .sidebarContent { 133 | display: none; 134 | } 135 | 136 | .sidebarCollapsedSubheader { 137 | display: block; 138 | animation: fadeIn 0.5s forwards; 139 | animation-delay: 400ms; 140 | } 141 | 142 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GestureSidebarRightExample/index.js: -------------------------------------------------------------------------------- 1 | // /* eslint-disable jsx-a11y/no-static-element-interactions */ 2 | // /* eslint-disable no-return-assign */ 3 | 4 | // import React, { Component } from 'react' 5 | // import { Flipper, Flipped } from '../../src' 6 | // import backgroundImg from './assets/nighttime.jpg' 7 | // import './styles.css' 8 | 9 | // class PaymentSidebar extends Component { 10 | // state = { collapsed: true } 11 | // toggleCollapsed = () => { 12 | // this.setState({ collapsed: !this.state.collapsed }) 13 | // } 14 | // render() { 15 | // const { collapsed } = this.state 16 | // const sidebarClassName = `sidebar ${collapsed ? 'sidebarCollapsed' : ''}` 17 | 18 | // return ( 19 | // 20 | // { 27 | // console.log(args) 28 | // } 29 | // }} 30 | // > 31 | //
    32 | // 33 | //
    34 | // 35 | //
    39 | // 40 | 41 | //
    42 | // 43 | //

    44 | // Lorem ipsum dolor sit amet consectetur 45 | //

    46 | //
    47 | //
    48 | // Lorem ipsum dolor, sit amet consectetur adipisicing elit. 49 | // Molestiae deleniti reprehenderit est necessitatibus qui iste 50 | // maiores enim amet atque nostrum? Facere ad eveniet 51 | // cupiditate molestiae, repellendus nisi consectetur quasi 52 | // adipisci. 53 | //
    54 | //
    55 | //
    56 | //
    57 | //
    58 | //
    59 | // 60 | // ) 61 | // } 62 | // } 63 | 64 | // export default PaymentSidebar 65 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GestureStaggeredList/styles.css: -------------------------------------------------------------------------------- 1 | /* .staggered-list-content { 2 | max-width: 400px; 3 | width: 90%; 4 | margin: 2rem auto; 5 | } 6 | .list { 7 | list-style-type: none; 8 | display: flex; 9 | flex-direction: column; 10 | height: 100vh; 11 | padding: 0; 12 | } 13 | .list li { 14 | width: 100%; 15 | cursor: grab; 16 | } 17 | .list li + li { 18 | margin-top: 1rem; 19 | } 20 | .listItem { 21 | background-color: #c4c4c4; 22 | width: 100%; 23 | cursor: pointer; 24 | } 25 | .listItemContent { 26 | display: flex; 27 | flex-direction: row; 28 | align-items: center; 29 | padding: 1rem; 30 | } 31 | .avatar { 32 | width: 4rem; 33 | height: 4rem; 34 | border-radius: 100px; 35 | background-color: hsla(0, 0%, 100%, 0.5); 36 | margin-right: 2rem; 37 | } 38 | .avatarExpanded { 39 | width: 8rem; 40 | height: 8rem; 41 | margin-right: 0; 42 | margin-bottom: 1rem; 43 | } 44 | .description > div { 45 | background-color: hsla(0, 0%, 100%, 0.5); 46 | width: 14rem; 47 | border-radius: 6px; 48 | height: 0.5rem; 49 | } 50 | .description > div:nth-of-type(2) { 51 | width: 11rem; 52 | } 53 | .description > div:nth-of-type(3) { 54 | width: 8rem; 55 | } 56 | .description > div + div { 57 | margin-top: 1rem; 58 | } 59 | .expandedListItem .description { 60 | display: flex; 61 | align-items: center; 62 | flex-direction: column; 63 | } 64 | .expandedListItem { 65 | cursor: pointer; 66 | background-color: #c4c4c4 !important; 67 | } 68 | .expandedListItemContent { 69 | padding: 2rem; 70 | display: flex; 71 | flex-direction: column; 72 | align-items: center; 73 | } 74 | @keyframes fadeIn { 75 | 0% { 76 | opacity: 0; 77 | transform: translateY(20px); 78 | } 79 | 100% { 80 | opacity: 1; 81 | } 82 | } 83 | .additional-content { 84 | width: 100%; 85 | margin-top: 2rem; 86 | } 87 | 88 | .additional-content > div { 89 | opacity: 0; 90 | border-radius: 3px; 91 | background-color: hsla(0, 0%, 100%, 0.5); 92 | height: 5rem; 93 | } 94 | 95 | .animated-in .additional-content > div { 96 | animation: fadeIn 0.4s forwards; 97 | } 98 | 99 | .additional-content > div:nth-of-type(2) { 100 | animation-delay: 0.15s; 101 | } 102 | .additional-content > div:nth-of-type(3) { 103 | animation-delay: 0.3s; 104 | } 105 | .additional-content > div + div { 106 | margin-top: 1rem; 107 | } */ 108 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/GuitarItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Flipped } from '../../src' 3 | 4 | class GuitarItem extends Component { 5 | static defaultProps = {} 6 | 7 | static propTypes = {} 8 | 9 | onStartImmediate = el => { 10 | el.style.zIndex = 10 11 | } 12 | 13 | onComplete = el => (el.style.zIndex = '') 14 | 15 | render() { 16 | const { index, title, subtitle, onClick, image } = this.props 17 | const parentId = `guitar-${index}` 18 | return ( 19 |
    20 |
    21 | 26 |
    27 | 28 | 33 | 34 | 35 |

    {title}

    36 |

    {subtitle}

    37 |
    38 |
    39 | ) 40 | } 41 | } 42 | 43 | export default GuitarItem 44 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/README.md: -------------------------------------------------------------------------------- 1 | # Expanding Grid Item Animation 2 | 3 | A product item animation where the background of a grid item expands and the product image scales up. Based on the Dribbble shot [Surf Project](https://dribbble.com/shots/3879463-Surf-Project) by [Filip Slováček](https://dribbble.com/filipslovacek). 4 | 5 | ![Expanding Grid Item Animation](https://tympanus.net/codrops/wp-content/uploads/2017/11/expandinggrid_featured.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=32944) 8 | 9 | [Demo](http://tympanus.net/Development/ExpandingGridItemAnimation/) 10 | 11 | ## Credits 12 | 13 | - [anime.js](http://anime-js.com/) by Julian Garnier 14 | - [imagesLoaded](http://imagesloaded.desandro.com/) by Dave DeSandro 15 | - Guitar icon by [iconnice](https://www.flaticon.com/authors/iconnice) 16 | - Guitar vector designed by [Freepik](http://www.freepik.com) 17 | - Patterns by [Pixel Buddha](https://pixelbuddha.net/) 18 | 19 | ## License 20 | This resource can be used freely if integrated or build upon in personal or commercial projects such as websites, web apps and web templates intended for sale. It is not allowed to take the resource "as-is" and sell it, redistribute, re-publish it, or sell "pluginized" versions of it. Free plugins built using this resource should have a visible mention and link to the original work. Always consider the licenses of all included libraries, scripts and images used. 21 | 22 | ## Misc 23 | 24 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [Google+](https://plus.google.com/101095823814290637419), [GitHub](https://github.com/codrops), [Pinterest](http://www.pinterest.com/codrops/), [Instagram](https://www.instagram.com/codropsss/) 25 | 26 | [© Codrops 2017](http://www.codrops.com) 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/SelectedGuitar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Flipped, spring } from '../../src' 3 | 4 | class SelectedGuitar extends Component { 5 | animateIn = () => { 6 | ;[...this.el.querySelectorAll('*[data-fade-in]')].forEach((el, i) => { 7 | spring({ 8 | values: { 9 | translateY: [-15, 0], 10 | opacity: [0, 1] 11 | }, 12 | onUpdate: ({ translateY, opacity }) => { 13 | el.style.opacity = opacity 14 | el.style.transform = `translateY(${translateY}px)` 15 | }, 16 | delay: i * 75 17 | }) 18 | }) 19 | } 20 | 21 | animateOut = () => { 22 | ;[...this.el.querySelectorAll('*[data-fade-in]')].forEach((el, i) => { 23 | spring({ 24 | values: { 25 | translateY: [0, -30], 26 | opacity: [1, 0] 27 | }, 28 | onUpdate: ({ translateY, opacity }) => { 29 | el.style.opacity = opacity 30 | el.style.transform = `translateY(${translateY}px)` 31 | }, 32 | delay: i * 50, 33 | onComplete: this.props.closeSelected 34 | }) 35 | }) 36 | } 37 | 38 | render() { 39 | const { title, subtitle, description, price, index, image } = this.props 40 | const parentId = `guitar-${index}` 41 | return ( 42 |
    (this.el = el)}> 43 |
    44 | 48 |
    49 | 50 | 51 | 52 | 53 |

    54 | {title} 55 |

    56 |
    64 |

    65 | {subtitle} 66 |

    67 |
    68 | ${price} 69 |
    70 |

    71 | {description} 72 |

    73 | 76 | 84 |
    85 | ) 86 | } 87 | } 88 | 89 | export default SelectedGuitar 90 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/favicon.ico -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/guitarsData.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | title: 'Marble Dream', 4 | subtitle: 'CONSTANTIN FRECKER', 5 | price: 129, 6 | description: 7 | 'Hashtag cred air plant drinking vinegar. Leggings yuccie chambray pop-up tousled hell of. Portland wolf mumblecore, synth cold-pressed polaroid poke cardigan gochujang farm-to-table photo booth.' 8 | }, 9 | { 10 | title: 'Space Fantasy', 11 | subtitle: 'DANICA GREEN', 12 | price: 199, 13 | description: 14 | 'Man bun banjo pop-up meh hammock. Skateboard hammock tousled retro, etsy taiyaki narwhal gentrify fixie food truck microdosing sustainable dreamcatcher.' 15 | }, 16 | { 17 | title: 'Mighty Eighties', 18 | subtitle: 'ELIZABETH SMITH', 19 | price: 159, 20 | description: 21 | 'Air plant affogato microdosing banjo, palo santo squid craft beer vexillologist chambray everyday carry cronut aesthetic intelligentsia.' 22 | }, 23 | { 24 | title: 'Diamond Crafter', 25 | subtitle: 'FRED HOUSE', 26 | price: 199, 27 | description: 28 | 'Crucifix shoreditch tumblr heirloom irony tbh gastropub migas sartorial mustache direct trade plaid readymade ramps hammock.' 29 | }, 30 | { 31 | title: 'Disco Fever', 32 | subtitle: 'ALICE MULLER', 33 | price: 99, 34 | description: 35 | 'Single-origin coffee air plant kitsch paleo iPhone vegan cold-pressed slow-carb cornhole dreamcatcher palo santo salvia lo-fi.' 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/1.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/10.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/2.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/3.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/4.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/5.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/6.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/7.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/8.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/9.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/related/GridItemAnimation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/related/GridItemAnimation.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/img/related/ImageGridEffects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/img/related/ImageGridEffects.png -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Flipper } from '../../src' 3 | import GuitarItem from './GuitarItem' 4 | import SelectedGuitar from './SelectedGuitar' 5 | import guitarsData from './guitarsData' 6 | import './css/base.css' 7 | import './pater/pater.css' 8 | 9 | import images from './img/*.png' 10 | 11 | class GuitarExample extends Component { 12 | static defaultProps = {} 13 | 14 | static propTypes = {} 15 | 16 | state = { 17 | focusedGuitarIndex: null 18 | } 19 | render() { 20 | return ( 21 | 22 |
    23 |
    24 |
    25 |

    26 | Expanding Grid Item Animation 27 |

    28 | 29 | 30 | Codrop's vanilla JS implementation 31 | 32 |   rewritten in React with react-flip-toolkit 33 | 34 |
    35 |
    36 |
    79 |
    80 | ) 81 | } 82 | } 83 | 84 | export default GuitarExample 85 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/pater/pater.css: -------------------------------------------------------------------------------- 1 | .pater { 2 | position: absolute; 3 | z-index: 1000; 4 | left: 2em; 5 | top: 2.5em; 6 | max-width: 200px; 7 | pointer-events: none; 8 | color: #000; 9 | } 10 | 11 | .pater::before { 12 | content: 'Our Sponsor'; 13 | position: absolute; 14 | bottom: calc(100% + 0.75em); 15 | left: 0; 16 | font-size: 0.65em; 17 | color: #a09897; 18 | } 19 | 20 | .pater__img { 21 | max-width: 100%; 22 | pointer-events: auto; 23 | display: block; 24 | filter: contrast(70%); 25 | transition: filter 0.3s; 26 | } 27 | 28 | .pater:hover .pater__img { 29 | filter: contrast(100%); 30 | } 31 | 32 | .pater__title { 33 | font-size: 0.975em; 34 | font-weight: 500; 35 | margin: 0; 36 | padding: 0.5em 0; 37 | pointer-events: auto; 38 | transition: transform 0.3s, opacity 0.3s; 39 | } 40 | 41 | .pater__desc { 42 | font-size: 0.85em; 43 | margin: 0; 44 | line-height: 1.5; 45 | pointer-events: none; 46 | opacity: 0; 47 | transform: translate3d(0,10px,0); 48 | transition: transform 0.3s, opacity 0.3s; 49 | } 50 | 51 | .pater__desc strong { 52 | display: block; 53 | margin: 0.5em 0 0 0; 54 | } 55 | 56 | @media screen and (min-width: 60em) { 57 | .pater:hover .pater__title { 58 | opacity: 0; 59 | transform: translate3d(-100%,0,0); 60 | } 61 | .pater:hover .pater__desc { 62 | pointer-events: auto; 63 | opacity: 1; 64 | transform: translate3d(0,-3.25em,0); 65 | } 66 | } 67 | 68 | @media screen and (max-width: 60em) { 69 | .pater { 70 | position: fixed; 71 | top: auto; 72 | bottom: 0; 73 | left: 0; 74 | display: flex; 75 | align-items: center; 76 | width: 100%; 77 | max-width: none; 78 | padding: 0.5em; 79 | text-align: left; 80 | pointer-events: auto; 81 | color: #fff !important; 82 | background: rgba(0, 0, 0, 0.9); 83 | } 84 | .pater::before { 85 | display: none; 86 | } 87 | .pater__img { 88 | flex: none; 89 | width: 50px; 90 | margin: 0; 91 | } 92 | .pater__title { 93 | font-size: 0.85em; 94 | font-weight: bold; 95 | padding: 0 0 0 1.5em; 96 | } 97 | .pater__desc { 98 | display: none; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/GuitarsExample/pater/storyblocks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/GuitarsExample/pater/storyblocks.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/HandleEnterUpdateDelete/getRandomList.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | return Array.from(new Array(100).keys()).filter(n => Math.random() > 0.5); 3 | }; 4 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/HandleEnterUpdateDelete/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Flipper, Flipped, ExitContainer, spring } from '../../src' 3 | import getRandomList from './getRandomList' 4 | import './styles.css' 5 | 6 | const simultaneousAnimations = ({ 7 | hideEnteringElements, 8 | animateEnteringElements, 9 | animateExitingElements, 10 | animateFlippedElements 11 | }) => { 12 | hideEnteringElements() 13 | animateExitingElements() 14 | animateFlippedElements() 15 | animateEnteringElements() 16 | } 17 | 18 | const exitThenFlipThenEnter = ({ 19 | hideEnteringElements, 20 | animateEnteringElements, 21 | animateExitingElements, 22 | animateFlippedElements 23 | }) => { 24 | hideEnteringElements() 25 | animateExitingElements() 26 | .then(animateFlippedElements) 27 | .then(animateEnteringElements) 28 | } 29 | 30 | const exitAndFlipThenEnter = ({ 31 | hideEnteringElements, 32 | animateEnteringElements, 33 | animateExitingElements, 34 | animateFlippedElements 35 | }) => { 36 | hideEnteringElements() 37 | Promise.all([animateExitingElements(), animateFlippedElements()]).then( 38 | animateEnteringElements 39 | ) 40 | } 41 | 42 | const transitions = { 43 | simultaneousAnimations, 44 | exitThenFlipThenEnter, 45 | exitAndFlipThenEnter 46 | } 47 | 48 | class EnterUpdateDeleteDemo extends Component { 49 | state = { 50 | list: getRandomList(), 51 | transitionType: 'exitThenFlipThenEnter', 52 | exitContainer: false 53 | } 54 | updateList = () => { 55 | this.setState({ list: getRandomList() }) 56 | } 57 | currentAnimations = [] 58 | onAppear = (el, i) => { 59 | this.currentAnimations.push( 60 | spring({ 61 | onUpdate: value => { 62 | el.style.opacity = value 63 | }, 64 | delay: i * 20 65 | }) 66 | ) 67 | } 68 | onExit = (el, i, onComplete) => { 69 | el.style.color = 'red' 70 | this.currentAnimations.push( 71 | spring({ 72 | 73 | onUpdate: value => { 74 | el.style.opacity = 1-value 75 | }, 76 | delay: i * 20, 77 | onComplete 78 | }) 79 | ) 80 | } 81 | render() { 82 | const flippedStuff = ( 83 |
    84 | {this.state.list.map(d => ( 85 | //break transitions by having a parent that is also removed 86 | // to demo ExitContainer use case 87 |
    88 | console.log(springValue)} 93 | > 94 |
  • {d}
  • 95 |
    96 |
    97 | ))} 98 |
    99 | ) 100 | 101 | return ( 102 |
    103 | 116 |
    117 | {Object.keys(transitions).map(transition => { 118 | return ( 119 | 132 | ) 133 | })} 134 |
    135 | 137 | console.log('complete', flipIds) 138 | } 139 | flipKey={this.state.list.join('')} 140 | element="ul" 141 | className="enter-update-delete-list" 142 | handleEnterUpdateDelete={callbacks => { 143 | this.currentAnimations.forEach(spring => spring.destroy()) 144 | this.currentAnimations = [] 145 | transitions[this.state.transitionType](callbacks) 146 | }} 147 | > 148 | {this.state.exitContainer ? ( 149 | {flippedStuff} 150 | ) : ( 151 | flippedStuff 152 | )} 153 | 154 |
    155 | ) 156 | } 157 | } 158 | 159 | export default EnterUpdateDeleteDemo 160 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/HandleEnterUpdateDelete/styles.css: -------------------------------------------------------------------------------- 1 | .enter-update-delete-container { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | padding: 2rem; 7 | } 8 | 9 | .enter-update-delete-list > div { 10 | display: flex; 11 | flex-wrap: wrap; 12 | list-style: none; 13 | width: 100%; 14 | } 15 | 16 | .enter-update-delete-list { 17 | width: 100%; 18 | } 19 | 20 | .enter-update-delete-list li { 21 | width: 2rem; 22 | display: inline-block; 23 | } 24 | 25 | .enterUpdateDeleteLabel { 26 | margin: 1rem; 27 | display: inline-block; 28 | } 29 | 30 | .enterUpdateDeleteLabel > input { 31 | margin-right: 0.5rem; 32 | } 33 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/ListExample/index.css: -------------------------------------------------------------------------------- 1 | .list-example { 2 | max-width: 900px; 3 | margin: 2rem auto; 4 | } 5 | 6 | .list-example h1 { 7 | font-size: 1.5rem; 8 | } 9 | 10 | .list-example .updating-list { 11 | list-style-type: none; 12 | display: grid; 13 | align-content: start; 14 | grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr)); 15 | grid-gap: 1rem; 16 | margin: 0; 17 | margin: 2rem auto; 18 | min-height: 15rem; 19 | } 20 | 21 | .list-example .listItem { 22 | height: 2.5rem; 23 | cursor: pointer; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | color: white; 28 | transform-origin: 0 0; 29 | transform: translate(30px, 15px); 30 | } 31 | 32 | .list-example .colorLabel { 33 | display: inline-block; 34 | height: 1.2rem; 35 | width: 2rem; 36 | margin-left: 0.5rem; 37 | } 38 | 39 | .list-example fieldset { 40 | border: 0; 41 | padding: 1rem; 42 | } 43 | 44 | .list-example label { 45 | margin-right: 1rem; 46 | cursor: pointer; 47 | } 48 | 49 | .list-example input { 50 | position: relative; 51 | margin-right: 0.5rem; 52 | } 53 | 54 | @keyframes fadeIn { 55 | 0% { 56 | opacity: 0; 57 | } 58 | 100% { 59 | opacity: 1; 60 | } 61 | } 62 | 63 | .list-example .fade-in { 64 | animation: fadeIn 0.5s forwards; 65 | } 66 | 67 | @keyframes fadeOut { 68 | 100% { 69 | opacity: 0; 70 | } 71 | } 72 | 73 | .fade-out-list-item { 74 | animation: fadeOut 0.5s forwards; 75 | } 76 | 77 | .list-flex { 78 | display: flex; 79 | } 80 | 81 | .list-toggle-updating { 82 | padding: 0.75rem 1rem; 83 | margin-top: 1rem; 84 | background-color: #240041; 85 | color: white; 86 | font-weight: bold; 87 | border: 0; 88 | border-radius: 3px; 89 | cursor: pointer; 90 | } 91 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/NestedFlipper/index.css: -------------------------------------------------------------------------------- 1 | .open-message { 2 | width: 400px; 3 | min-height: 50px; 4 | } 5 | 6 | .closed-message { 7 | width: 50px; 8 | min-height: 50px; 9 | } 10 | 11 | .message { 12 | background: rgb(150, 80, 45); 13 | padding: 8px; 14 | } 15 | 16 | .message-flipper { 17 | display: flex; 18 | } 19 | 20 | .message-item { 21 | margin: 8px 0; 22 | } 23 | 24 | .message-item-content { 25 | display: inline-flex; 26 | } 27 | 28 | .message-item-open { 29 | width: 300px; 30 | height: 10px; 31 | padding: 8px; 32 | background: rgb(45, 80, 150); 33 | } 34 | 35 | .message-item-closed { 36 | width: 40px; 37 | height: 10px; 38 | padding: 8px; 39 | background: rgb(45, 80, 150); 40 | } 41 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/NestedFlipper/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Flipper, Flipped } from "../../src/index" 3 | import './index.css' 4 | 5 | class MessageItem extends Component { 6 | state = { 7 | open: false 8 | } 9 | 10 | toggleOpen = () => { 11 | this.setState(s => ({ open: !s.open })) 12 | } 13 | 14 | render() { 15 | const { i } = this.props 16 | const { open } = this.state 17 | 18 | return ( 19 |
    20 | 21 | 22 |
    25 | {open ? ( 26 |
    27 | ) : ( 28 |
    29 | )} 30 | 31 |
    32 | 33 | 34 |
    35 | ) 36 | } 37 | } 38 | 39 | const OpenMessage = () => { 40 | return ( 41 |
    42 | 43 | 44 | 45 |
    46 | ) 47 | } 48 | 49 | const ClosedMessage = () => { 50 | return
    51 | } 52 | 53 | class Message extends Component { 54 | state = { 55 | open: false 56 | } 57 | 58 | toggleOpen = () => { 59 | this.setState(s => ({ open: !s.open })) 60 | } 61 | 62 | render() { 63 | const { open } = this.state 64 | 65 | return ( 66 | 67 | 68 | 69 |
    70 | {open ? : } 71 |
    72 |
    73 |
    74 | ) 75 | } 76 | } 77 | 78 | export default Message 79 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PhotoGridExample/assets/detail-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/PhotoGridExample/assets/detail-1.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PhotoGridExample/assets/detail-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/PhotoGridExample/assets/detail-5.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PhotoGridExample/assets/detail-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/PhotoGridExample/assets/detail-6.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PhotoGridExample/assets/detail-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/PhotoGridExample/assets/detail-8.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PhotoGridExample/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=DM+Sans&display=swap'); 2 | 3 | .photoGridExample { 4 | font-family: 'DM Sans'; 5 | color: black; 6 | } 7 | 8 | .photoGrid { 9 | display: grid; 10 | grid-gap: 3rem; 11 | grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); 12 | grid-auto-rows: calc(100vh / 2 - 6rem); 13 | justify-items: center; 14 | margin: 3rem; 15 | } 16 | 17 | .photoGridTitle { 18 | font-size: 1rem; 19 | position: absolute; 20 | left: 33%; 21 | margin-top: 0; 22 | } 23 | 24 | .photoGridSquare { 25 | cursor: pointer; 26 | width: 100%; 27 | height: 100%; 28 | position: relative; 29 | } 30 | 31 | .photoGridShader { 32 | background-image: linear-gradient(hsla(0, 0%, 100%, 0.3), white); 33 | position: absolute; 34 | bottom: -5px; 35 | left: 0; 36 | right: 0; 37 | top: 0; 38 | opacity: 1; 39 | z-index: 1; 40 | } 41 | 42 | .photoGridImg { 43 | width: 100%; 44 | height: 90%; 45 | object-fit: cover; 46 | position: relative; 47 | will-change: transform; 48 | } 49 | 50 | .photoGridSquareExpanded { 51 | position: fixed; 52 | top: 0; 53 | left: 0; 54 | right: 0; 55 | bottom: 0; 56 | z-index: 2; 57 | cursor: pointer; 58 | } 59 | 60 | .photoGridSquareExpanded .photoGridImg { 61 | position: absolute; 62 | height: 100%; 63 | will-change: transform; 64 | } 65 | 66 | .photoGridContentContainer { 67 | width: 830px; 68 | margin: auto; 69 | position: absolute; 70 | top: 8vh; 71 | left: 50%; 72 | transform: translateX(-50%); 73 | font-size: 1.6rem; 74 | line-height: 1.6; 75 | z-index: 4; 76 | } 77 | 78 | .photoHeading { 79 | position: relative; 80 | z-index: 2; 81 | font-size: 1.5rem; 82 | margin: 1rem 0; 83 | } 84 | 85 | .photoHeadingFocused { 86 | font-size: 3rem; 87 | margin: 1rem 0; 88 | } 89 | 90 | .photoGridShaderHidden { 91 | opacity: 0; 92 | } 93 | 94 | .photoGridExample [data-fade-in] { 95 | opacity: 0; 96 | } 97 | 98 | .photoGridLead { 99 | font-size: 2rem; 100 | font-weight: 200; 101 | margin-bottom: 2rem; 102 | } 103 | 104 | .photoGridFocused { 105 | position: absolute; 106 | top: 0; 107 | width: 100%; 108 | } 109 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/IconGrid/Modal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | import { createPortal } from "react-dom" 3 | import { Flipped } from "../../../src" 4 | 5 | class Modal extends Component { 6 | static propTypes = {} 7 | 8 | render() { 9 | return createPortal( 10 |
    16 | {!this.props.animatingOut && ( 17 | (el.style.zIndex = 10)} 20 | onComplete={el => (el.style.zIndex = "")} 21 | > 22 | 23 | 24 | )} 25 |
    , 26 | this.props.container 27 | ) 28 | } 29 | } 30 | 31 | export default Modal 32 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/IconGrid/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | import { Flipper, Flipped } from "../../../src/index"; 3 | 4 | import Modal from "./Modal" 5 | 6 | class PortalExample extends Component { 7 | static propTypes = {} 8 | 9 | state = { 10 | focusedIcon: false, 11 | modalAnimatingOut: false 12 | } 13 | 14 | closeModal = () => { 15 | this.setState({ modalAnimatingOut: true, focusedIcon: false }) 16 | setTimeout(() => { 17 | this.setState({ 18 | modalAnimatingOut: false 19 | }) 20 | }, 400) 21 | } 22 | 23 | componentDidMount = () => { 24 | this.modalContainer = document.createElement("div") 25 | document.querySelector("body").appendChild(this.modalContainer) 26 | } 27 | componentWillUnmount() { 28 | document.querySelector("body").removeChild(this.modalContainer) 29 | } 30 | 31 | render() { 32 | const { icons, portalKey, title } = this.props 33 | return ( 34 |
    35 | 36 |
    37 |

    {title}

    38 |
      39 | {icons.map((icon, i) => { 40 | if ( 41 | i === this.state.focusedIcon && 42 | !this.state.modalAnimatingOut 43 | ) 44 | return
    • 45 | return ( 46 |
    • 47 | (el.style.zIndex = 10)} 50 | onComplete={el => (el.style.zIndex = "")} 51 | > 52 | { 56 | this.setState({ focusedIcon: i }) 57 | }} 58 | /> 59 | 60 |
    • 61 | ) 62 | })} 63 |
    64 |
    65 | {(typeof this.state.focusedIcon === "number" || 66 | this.state.modalAnimatingOut) && ( 67 | 75 | )} 76 |
    77 |
    78 | ) 79 | } 80 | } 81 | 82 | export default PortalExample 83 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/creative/art_palette.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 18 | 20 | 21 | 23 | 25 | 27 | 29 | 31 | 33 | 34 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/creative/brush.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/creative/coffee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | 13 | 18 | 22 | 26 | 27 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/creative/device.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 27 | 28 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/creative/fountain_pen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 11 | 12 | 13 | 14 | 22 | 24 | 26 | 27 | 29 | 31 | 33 | 35 | 37 | 38 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/environment/bio_energy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 12 | 15 | 17 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/environment/eco_friendly_vehicle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 15 | 25 | 26 | 27 | 29 | 30 | 34 | 36 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/environment/electronic_recycling.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 12 | 14 | 16 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/environment/energy_saving_lightbulb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 12 | 15 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/assets/environment/global_warming.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import "./styles.css"; 4 | 5 | import IconGrid from "./IconGrid"; 6 | import creative1 from "./assets/creative/art_palette.svg"; 7 | import creative2 from "./assets/creative/brush.svg"; 8 | import creative3 from "./assets/creative/coffee.svg"; 9 | import creative4 from "./assets/creative/fountain_pen.svg"; 10 | 11 | import environment1 from "./assets/environment/bio_energy.svg"; 12 | import environment2 from "./assets/environment/eco_friendly_vehicle.svg"; 13 | import environment3 from "./assets/environment/energy_saving_lightbulb.svg"; 14 | import environment4 from "./assets/environment/global_warming.svg"; 15 | 16 | const creativeIcons = [creative1, creative2, creative3, creative4]; 17 | 18 | const environmentIcons = [ 19 | environment1, 20 | environment2, 21 | environment3, 22 | environment4 23 | ]; 24 | 25 | class PortalExample extends Component { 26 | 27 | render() { 28 | return ( 29 |
    30 | 35 | 40 |
    41 | ); 42 | } 43 | } 44 | 45 | export default PortalExample; 46 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/PortalExample/styles.css: -------------------------------------------------------------------------------- 1 | .portal-item { 2 | background-color: aqua; 3 | width: 5rem; 4 | height: 5rem; 5 | } 6 | 7 | .portal-item--modal { 8 | width: 7rem; 9 | height: 10rem; 10 | } 11 | 12 | @keyframes ModalFadeIn { 13 | 0% { 14 | transform: translateX(-100vw); 15 | } 16 | 100% { 17 | transform: translateY(0); 18 | } 19 | } 20 | 21 | @keyframes ModalFadeOut { 22 | 100% { 23 | transform: translateX(100vw); 24 | } 25 | } 26 | 27 | @keyframes ModalFadeInOpposite { 28 | 0% { 29 | transform: translateX(100vw); 30 | } 31 | 100% { 32 | transform: translateY(0); 33 | } 34 | } 35 | 36 | @keyframes ModalFadeOutOpposite { 37 | 100% { 38 | transform: translateX(-100vw); 39 | } 40 | } 41 | 42 | .portal-modal { 43 | position: fixed; 44 | top: 0; 45 | left: 0; 46 | bottom: 0; 47 | right: 0; 48 | display: flex; 49 | justify-content: center; 50 | align-items: center; 51 | cursor: pointer; 52 | } 53 | 54 | .portal-modal::after { 55 | content: ""; 56 | position: absolute; 57 | top: 0; 58 | left: 0; 59 | bottom: 0; 60 | right: 0; 61 | animation: ModalFadeIn 0.4s forwards; 62 | z-index: 0; 63 | background-color: #66cdfa; 64 | } 65 | 66 | .portal-modal.environment::after { 67 | background-color: #93e7a3; 68 | animation: ModalFadeInOpposite 0.4s forwards; 69 | } 70 | 71 | .portal-modal--animating-out::after { 72 | animation: ModalFadeOut 0.4s forwards; 73 | } 74 | 75 | .portal-modal--animating-out.environment::after { 76 | animation: ModalFadeOutOpposite 0.4s forwards; 77 | } 78 | 79 | .portal-image-list { 80 | list-style: none; 81 | display: grid; 82 | grid-template-columns: repeat(2, minmax(5rem, 1fr)); 83 | grid-gap: 2rem; 84 | margin: 0; 85 | padding: 0; 86 | } 87 | 88 | .portal-image-list li { 89 | display: flex; 90 | justify-content: center; 91 | align-items: center; 92 | } 93 | 94 | .portal-image-list img { 95 | width: 100%; 96 | height: auto; 97 | cursor: pointer; 98 | } 99 | 100 | .focused-portal-img { 101 | width: 500px; 102 | height: auto; 103 | position: relative; 104 | z-index: 2; 105 | } 106 | 107 | .portal-grid-container { 108 | display: grid; 109 | grid-gap: 7rem; 110 | grid-template-columns: repeat(2, 15rem); 111 | justify-content: center; 112 | align-items: center; 113 | height: 100vh; 114 | } 115 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/RemountedFlipperExample/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Flipper, Flipped } from "../../src/index"; 3 | 4 | export default class AnimatedSquare extends Component { 5 | state = { showFlip: false }; 6 | 7 | componentDidMount = () => { 8 | setTimeout(() => { 9 | this.setState({ 10 | showFlip: true 11 | }); 12 | }, 1); 13 | }; 14 | 15 | render() { 16 | return this.state.showFlip 17 | ? 18 | 19 |
    hi
    20 |
    21 |
    22 | : 23 |
    hi
    24 |
    ; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/RotateExample/index.css: -------------------------------------------------------------------------------- 1 | .rotate { 2 | padding: 2rem; 3 | } 4 | 5 | .rotate-square { 6 | width: 5rem; 7 | height: 5rem; 8 | display: block; 9 | cursor: pointer; 10 | transform: rotate(10deg); 11 | } 12 | 13 | .rotate-square--focused { 14 | cursor: pointer; 15 | transform: rotate(45deg); 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/RotateExample/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Flipper, Flipped } from "../../src/index"; 3 | import "./index.css"; 4 | 5 | const color = "#ff4f66"; 6 | 7 | class RotateExample extends Component { 8 | state = { focused: undefined }; 9 | 10 | render() { 11 | return ( 12 |
    13 | 14 | 15 |
    22 | this.setState({ 23 | focused: this.state.focused === color ? null : color 24 | })} 25 | /> 26 | 27 | 28 |
    31 | 32 | 33 | 34 |
    37 | 38 | 39 |
    40 | ); 41 | } 42 | } 43 | 44 | export default RotateExample; 45 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/SidebarExample/assets/nighttime.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/SidebarExample/assets/nighttime.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/SidebarExample/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 2 | /* eslint-disable no-return-assign */ 3 | 4 | import React, { Component } from 'react' 5 | import { Flipper, Flipped } from "../../src/index" 6 | import backgroundImg from './assets/nighttime.jpg' 7 | import './styles.css' 8 | 9 | class PaymentSidebar extends Component { 10 | state = { collapsed: false } 11 | toggleCollapsed = () => { 12 | this.setState({ collapsed: !this.state.collapsed }) 13 | } 14 | render() { 15 | const { collapsed } = this.state 16 | const sidebarClassName = `sidebar ${collapsed ? 'sidebarCollapsed' : ''}` 17 | 18 | return ( 19 | console.log('on complete', args)}> 20 | { 23 | console.log(springValue) 24 | }} 25 | > 26 |
    27 | 28 |
    29 | 30 |
    34 | 35 | 36 |
    37 | 38 |

    39 | Lorem ipsum dolor sit amet consectetur 40 |

    41 |
    42 |
    43 | Lorem ipsum dolor, sit amet consectetur adipisicing elit. 44 | Molestiae deleniti reprehenderit est necessitatibus qui iste 45 | maiores enim amet atque nostrum? Facere ad eveniet 46 | cupiditate molestiae, repellendus nisi consectetur quasi 47 | adipisci. 48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 | 55 | ) 56 | } 57 | } 58 | 59 | export default PaymentSidebar 60 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/SidebarExample/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --sidebar-primary: black; 3 | } 4 | 5 | .sidebar { 6 | color: white; 7 | position: relative; 8 | z-index: 1; 9 | height: 100vh; 10 | width: 50rem; 11 | overflow: hidden; 12 | min-height: 50rem; 13 | background-color: var(--sidebar-primary); 14 | cursor: pointer; 15 | backface-visibility: hidden; 16 | } 17 | 18 | .sidebar.sidebarCollapsed { 19 | min-height: auto; 20 | } 21 | 22 | .sidebarHeader { 23 | font-size: 2rem; 24 | line-height: 1.5; 25 | font-weight: bold; 26 | color: #fff; 27 | transform-origin: 0 0; 28 | display: inline-block; 29 | width: 100%; 30 | margin-bottom: 0.5rem; 31 | user-select: none; 32 | } 33 | 34 | .sidebarCollapsedSubheader { 35 | display: none; 36 | opacity: 0; 37 | position: absolute; 38 | left: 34%; 39 | top: 3rem; 40 | font-size: 1.1rem; 41 | } 42 | 43 | .sidebarBody { 44 | padding-left: 1.75rem; 45 | padding-right: 1.75rem; 46 | padding-bottom: 1.75rem; 47 | padding-top: 10vh; 48 | z-index: 2; 49 | position: relative; 50 | } 51 | 52 | .decorativeImg { 53 | height: 18rem; 54 | width: 100%; 55 | background-repeat: no-repeat; 56 | background-size: cover; 57 | background-position: 50% 50%; 58 | position: absolute; 59 | z-index: 0; 60 | transform-origin: 0 0; 61 | } 62 | 63 | .decorativeImg::after { 64 | content: ''; 65 | position: absolute; 66 | top: 0; 67 | left: 0; 68 | right: 0; 69 | bottom: 0; 70 | z-index: 1; 71 | background: linear-gradient(transparent, hsla(0, 0%, 0%, 1)); 72 | } 73 | 74 | .sidebarBtn { 75 | margin-top: 1rem; 76 | } 77 | 78 | @keyframes fadeIn { 79 | 0% { 80 | opacity: 0; 81 | } 82 | 100% { 83 | opacity: 1; 84 | } 85 | } 86 | 87 | .openButton { 88 | position: absolute; 89 | right: 1rem; 90 | top: 50%; 91 | transform: translateY(-50%); 92 | animation: fadeIn 0.5s forwards; 93 | animation-delay: 400ms; 94 | background: transparent; 95 | border: 0; 96 | opacity: 0; 97 | } 98 | 99 | .toggleOpenIcon { 100 | color: #fff; 101 | width: 1.2rem; 102 | height: 1.2rem; 103 | } 104 | 105 | .sidebarCollapsed { 106 | height: 5rem; 107 | } 108 | 109 | .sidebarCollapsed .decorativeImg { 110 | width: 28%; 111 | height: 100%; 112 | } 113 | 114 | .sidebarCollapsed::after { 115 | opacity: 0; 116 | } 117 | 118 | .sidebarCollapsed .sidebarBody { 119 | padding-top: 0; 120 | } 121 | 122 | .sidebarCollapsed .sidebarHeader { 123 | position: relative; 124 | top: 1.1rem; 125 | left: 34%; 126 | font-size: 1rem; 127 | overflow: hidden; 128 | white-space: nowrap; 129 | max-width: calc(66% - 3rem); 130 | text-overflow: ellipsis; 131 | } 132 | 133 | .sidebarCollapsed .sidebarContent { 134 | display: none; 135 | } 136 | 137 | .sidebarCollapsedSubheader { 138 | display: block; 139 | animation: fadeIn 0.5s forwards; 140 | animation-delay: 400ms; 141 | } 142 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/StaggeredList/index.js: -------------------------------------------------------------------------------- 1 | // inspired by this animated demo: 2 | // https://uxplanet.org/animation-in-ui-design-from-concept-to-reality-85c49907b19d 3 | import React, { Component } from 'react' 4 | import { Flipper, Flipped } from '../../src/index' 5 | import './styles.css' 6 | 7 | const listData = [...Array(7).keys()]; 8 | const createCardFlipId = index => `listItem-${index}`; 9 | 10 | const shouldFlip = index => (prev, current) => 11 | index === prev || index === current; 12 | 13 | const ListItem = ({ index, onClick }) => { 14 | return ( 15 | 20 |
    onClick(index)}> 21 | 22 |
    23 | 29 |
    30 | 31 |
    32 | {listData.slice(0, 3).map(i => ( 33 | 39 |
    40 | 41 | ))} 42 |
    43 |
    44 | 45 |
    46 |
    47 | ); 48 | }; 49 | 50 | const ExpandedListItem = ({ index, onClick }) => { 51 | return ( 52 | { 56 | setTimeout(() => { 57 | el.classList.add("animated-in"); 58 | }, 400); 59 | }} 60 | > 61 |
    onClick(index)}> 62 | 63 |
    64 | 69 |
    70 | 71 |
    72 | {listData.slice(0, 3).map(i => ( 73 | 78 |
    79 | 80 | ))} 81 |
    82 |
    83 | {listData.slice(0, 3).map(i => ( 84 |
    85 | ))} 86 |
    87 |
    88 |
    89 |
    90 | 91 | ); 92 | }; 93 | 94 | export default class AnimatedList extends Component { 95 | state = { focused: null }; 96 | onClick = index => 97 | this.setState({ 98 | focused: this.state.focused === index ? null : index 99 | }); 100 | render() { 101 | return ( 102 | 113 |
      114 | {listData.map(index => { 115 | return ( 116 |
    • 117 | {index === this.state.focused ? ( 118 | 122 | ) : ( 123 | 124 | )} 125 |
    • 126 | ); 127 | })} 128 |
    129 |
    130 | ); 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/StaggeredList/styles.css: -------------------------------------------------------------------------------- 1 | .staggered-list-content { 2 | width: 400px; 3 | margin: 2rem auto; 4 | } 5 | .list { 6 | list-style-type: none; 7 | display: flex; 8 | flex-direction: column; 9 | height: 100vh; 10 | padding: 0; 11 | } 12 | .list li { 13 | width: 100%; 14 | } 15 | .list li + li { 16 | margin-top: 1rem; 17 | } 18 | .listItem { 19 | width: 100%; 20 | cursor: pointer; 21 | background-color: #d0d0d0; 22 | overflow: hidden; 23 | } 24 | .listItemContent { 25 | display: flex; 26 | flex-direction: row; 27 | align-items: center; 28 | padding: 1rem; 29 | } 30 | .avatar { 31 | width: 4rem; 32 | height: 4rem; 33 | border-radius: 100px; 34 | background-color: grey; 35 | margin-right: 2rem; 36 | } 37 | .avatarExpanded { 38 | width: 8rem; 39 | height: 8rem; 40 | margin-right: 0; 41 | margin-bottom: 1rem; 42 | } 43 | .description > div { 44 | background-color: grey; 45 | width: 14rem; 46 | border-radius: 6px; 47 | height: 0.5rem; 48 | } 49 | .description > div:nth-of-type(2) { 50 | width: 11rem; 51 | } 52 | .description > div:nth-of-type(3) { 53 | width: 8rem; 54 | } 55 | .description > div + div { 56 | margin-top: 1rem; 57 | } 58 | .expandedListItem .description { 59 | display: flex; 60 | align-items: center; 61 | flex-direction: column; 62 | } 63 | .expandedListItem { 64 | cursor: pointer; 65 | background-color: #d0d0d0; 66 | } 67 | .expandedListItemContent { 68 | padding: 2rem; 69 | display: flex; 70 | flex-direction: column; 71 | align-items: center; 72 | } 73 | .additional-content { 74 | width: 100%; 75 | margin-top: 2rem; 76 | } 77 | 78 | .additional-content > div { 79 | opacity: 0; 80 | border-radius: 3px; 81 | background-color: grey; 82 | height: 5rem; 83 | } 84 | 85 | /* content fade in animations */ 86 | @keyframes fadeIn { 87 | 0% { 88 | opacity: 0; 89 | transform: translateY(20px); 90 | } 91 | 100% { 92 | opacity: 1; 93 | } 94 | } 95 | 96 | .animated-in .additional-content > div { 97 | animation: fadeIn 0.4s forwards; 98 | } 99 | 100 | .additional-content > div:nth-of-type(2) { 101 | animation-delay: 0.15s; 102 | } 103 | .additional-content > div:nth-of-type(3) { 104 | animation-delay: 0.3s; 105 | } 106 | .additional-content > div + div { 107 | margin-top: 1rem; 108 | } 109 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/TransformExample/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Flipper, Flipped } from "../../src/index"; 3 | import "./styles.css"; 4 | 5 | const SmallSquare = ({ onClick, ...rest }) => 6 |
    ; 7 | 8 | const BigSquare = ({ onClick, ...rest }) => 9 |
    ; 10 | 11 | export default class AnimatedSquare extends Component { 12 | state = { fullScreen: false }; 13 | 14 | toggleFullScreen = () => { 15 | this.setState(prevState => ({ 16 | fullScreen: !prevState.fullScreen 17 | })); 18 | }; 19 | 20 | render() { 21 | return ( 22 | 26 | {this.state.fullScreen 27 | ? 28 | 29 | 30 | : 31 | 32 | } 33 | 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/TransformExample/styles.css: -------------------------------------------------------------------------------- 1 | .square { 2 | width: 200px; 3 | height: 200px; 4 | cursor: pointer; 5 | background-color: #7971ea; 6 | } 7 | 8 | .full-screen-square { 9 | position: relative; 10 | left: 50%; 11 | top: 50%; 12 | transform: translate(-50%, -50%); 13 | cursor: pointer; 14 | } 15 | 16 | .transform-example-container { 17 | height: 100vh; 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/TransformExampleExitingParent/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, useState } from 'react' 2 | import { Flipper, Flipped } from "../../src/index" 3 | import './styles.css' 4 | 5 | const Square = ({ toggleFullScreen }) => ( 6 | 7 |
    8 | 9 | ) 10 | 11 | const FullScreenSquare = ({ toggleFullScreen }) => ( 12 | 13 |
    14 | 15 | ) 16 | 17 | const AnimatedSquare = () => { 18 | const [fullScreen, setFullScreen] = useState(false) 19 | const toggleFullScreen = () => setFullScreen(prevState => !prevState) 20 | 21 | return ( 22 | 23 | {fullScreen ? ( 24 | 25 |
    26 | 27 |
    28 |
    29 | ) : ( 30 | 31 |
    32 | 33 |
    34 |
    35 | )} 36 |
    37 | ) 38 | } 39 | 40 | export default AnimatedSquare 41 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/TransformExampleExitingParent/styles.css: -------------------------------------------------------------------------------- 1 | .square { 2 | width: 200px; 3 | height: 200px; 4 | cursor: pointer; 5 | background-color: #7971ea; 6 | } 7 | 8 | .full-screen-square { 9 | position: relative; 10 | left: 50%; 11 | top: 50%; 12 | transform: translate(-50%, -50%); 13 | cursor: pointer; 14 | } 15 | 16 | .transform-example-container { 17 | height: 100vh; 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/TransformFromZeroExample/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Flipper, Flipped } from "../../src/index"; 3 | import "./styles.css"; 4 | 5 | const SmallSquare = ({ onClick, ...rest }) => 6 |
    ; 7 | 8 | const BigSquare = ({ onClick, ...rest }) => 9 |
    ; 14 | 15 | export default class AnimatedSquare extends Component { 16 | state = { fullScreen: false }; 17 | 18 | toggleFullScreen = () => { 19 | this.setState(prevState => ({ 20 | fullScreen: !prevState.fullScreen 21 | })); 22 | }; 23 | 24 | componentDidMount() { 25 | setInterval(() => { 26 | this.toggleFullScreen(); 27 | }, 1000); 28 | } 29 | 30 | render() { 31 | return ( 32 | 36 | {this.state.fullScreen 37 | ? 38 | 39 | 40 | : 41 | 42 | } 43 | 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/TransformFromZeroExample/styles.css: -------------------------------------------------------------------------------- 1 | .zero-square { 2 | width: 15rem; 3 | height: 15rem; 4 | cursor: pointer; 5 | background-color: #7971ea; 6 | } 7 | 8 | .zero-full-screen-square { 9 | height: 0; 10 | width: 0; 11 | cursor: pointer; 12 | } 13 | 14 | .zero-transform-example-container { 15 | padding: 5rem; 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/ZeroJumpExample/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Flipper, Flipped } from "react-flip-toolkit"; 3 | import shuffle from "lodash.shuffle"; 4 | 5 | export default function TransformZeroExample () { 6 | const [data, setData] = useState([...Array(200).keys()]); 7 | const shuffleList = () => setData(shuffle(data)); 8 | 9 | // Say user has filtered to only show multiples of 50 10 | const n = 50; 11 | 12 | return ( 13 | 14 | 15 |
    16 | {data.map((d) => ( 17 | 18 |
    19 | {d % n === 0 ? d : null} 20 |
    21 |
    22 | ))} 23 |
    24 |
    25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/australian-shepard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/australian-shepard.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/black-doodle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/black-doodle.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/border-collie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/border-collie.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/charles-451760-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/charles-451760-unsplash.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/corgi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/corgi.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/fredrik-ohlander-520696-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/fredrik-ohlander-520696-unsplash.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/french-bulldog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/french-bulldog.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/friends.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/friends.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/golden-with-flower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/golden-with-flower.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/husky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/husky.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/ian-dooley-325434-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/ian-dooley-325434-unsplash.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/ipet-photo-1316903-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/ipet-photo-1316903-unsplash.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/oscar-sutton-719939-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/oscar-sutton-719939-unsplash.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/pug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/pug.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/assets/dogs/samoyed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aholachek/react-flip-toolkit/af8be42ce05045495effa687dac73e542db7cd45/packages/react-flip-toolkit/demo/assets/dogs/samoyed.jpg -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Demo 8 | 9 | 10 | 13 | 14 | 15 | 16 |
    17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/demo/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | 3 | import React, { Component } from 'react' 4 | import { render } from 'react-dom' 5 | import 'normalize.css' 6 | import CardsExample from './CardsExample' 7 | import GuitarsExample from './GuitarsExample' 8 | import SidebarExample from './SidebarExample' 9 | import PhotosExample from './PhotoGridExample' 10 | import ListExample from './ListExample' 11 | import FlipMove from './FlipMove' 12 | import TransformExample from './TransformExample' 13 | import TransformExampleExitingParent from './TransformExampleExitingParent' 14 | 15 | import PortalExample from './PortalExample' 16 | import TransformFromZeroExample from './TransformFromZeroExample' 17 | import RotateExample from './RotateExample' 18 | import StaggeredList from './StaggeredList' 19 | import RemountedFlipperExample from './RemountedFlipperExample' 20 | import HandleEnterUpdateDelete from './HandleEnterUpdateDelete' 21 | import NestedFlipper from './NestedFlipper' 22 | 23 | import GestureSidebarExample from './GestureSidebarExample' 24 | import GestureStaggeredList from './GestureStaggeredList' 25 | import FancyDrawerSwipe from './FancyDrawerSwipe' 26 | import ZeroJumpExample from './ZeroJumpExample' 27 | 28 | class Demo extends Component { 29 | render() { 30 | if (window.location.pathname === '/cards') return 31 | else if (window.location.pathname === '/guitar') return 32 | else if (window.location.pathname === '/sidebar') return 33 | else if (window.location.pathname === '/photos') return 34 | else if (window.location.pathname === '/list') return 35 | else if (window.location.pathname === '/flip-move') return 36 | else if (window.location.pathname === '/transform') 37 | return 38 | else if (window.location.pathname === '/transform-exiting-parent') 39 | return 40 | else if (window.location.pathname === '/portal') return 41 | else if (window.location.pathname === '/transform-from-zero') 42 | return 43 | else if (window.location.pathname === '/rotate') return 44 | else if (window.location.pathname === '/staggered-list') 45 | return 46 | else if (window.location.pathname === '/remounted-flipper') 47 | return 48 | else if (window.location.pathname === '/enter-update-delete') 49 | return 50 | else if (window.location.pathname === '/zero-jump') 51 | return 52 | else if (window.location.pathname === '/nested-flipper') 53 | return 54 | else 55 | return ( 56 | 104 | ) 105 | } 106 | } 107 | 108 | render(, document.querySelector('#demo')) 109 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-flip-toolkit", 3 | "version": "7.2.4", 4 | "description": "Configurable FLIP animation helpers for React", 5 | "license": "MIT", 6 | "source": "src/index.ts", 7 | "main": "lib/index.js", 8 | "module": "lib/index.es.js", 9 | "types": "lib/index.d.ts", 10 | "amdName": "ReactFlipToolkit", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/aholachek/react-flip-toolkit" 14 | }, 15 | "bugs": "https://github.com/aholachek/react-flip-toolkit/issues", 16 | "author": "Alex Holachek", 17 | "keywords": [ 18 | "react-component", 19 | "FLIP", 20 | "transition", 21 | "animation" 22 | ], 23 | "engines": { 24 | "node": ">=8", 25 | "npm": ">=5" 26 | }, 27 | "scripts": { 28 | "microbundle": "microbundle --define process.env.NODE_ENV=production --name=ReactFlipToolkit --jsx React.createElement --globals react=React,prop-types=PropTypes,flip-toolkit=FlipToolkit", 29 | "start": "parcel --no-cache demo/index.html --out-dir demo/lib", 30 | "deploy-demo": "parcel build demo/index.html --out-dir demo/lib; cp demo/lib/index.html demo/lib/200.html; surge demo/lib --domain react-flip-toolkit-demos.surge.sh", 31 | "build": "cp ../../README.md ./README.md; yarn microbundle", 32 | "build:debug": "microbundle --name=ReactFlipToolkit --jsx React.createElement --no-compress", 33 | "watch": "watch 'npm run build:no-compress' ./src", 34 | "build:no-compress": "microbundle --no-compress", 35 | "check-types": "tsc --noEmit", 36 | "prepare": "yarn run build", 37 | "predeploy": "cd example && yarn install && yarn run build", 38 | "inspect": "npx source-map-explorer lib/", 39 | "lint": "eslint src/**/*.{ts,tsx}", 40 | "format": "npx prettier --write 'src/**/*.{ts,tsx}'", 41 | "fix": "eslint src/**/*.{ts,tsx} --fix", 42 | "format-and-fix": "npm-run-all format fix", 43 | "test": "jest --testPathIgnorePatterns \"lib|domtest\"", 44 | "preversion": "npm test", 45 | "postpublish": "git push origin master; git push origin --tags; rm ./README.MD", 46 | "version": "npm run build", 47 | "prepublish": "npm run build" 48 | }, 49 | "dependencies": { 50 | "flip-toolkit": "7.2.4", 51 | "prop-types": "^15.8.1" 52 | }, 53 | "peerDependencies": { 54 | "react": ">= 16.x", 55 | "react-dom": ">= 16.x" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.24.7", 59 | "@babel/preset-env": "^7.24.7", 60 | "@babel/preset-react": "^7.24.7", 61 | "@babel/preset-typescript": "^7.24.7", 62 | "@babel/runtime": "^7.24.7", 63 | "@emotion/core": "^10.3.1", 64 | "@types/jest": "^29.5.12", 65 | "@types/react": "^18.3.3", 66 | "@types/react-dom": "^18.3.0", 67 | "@types/react-test-renderer": "^18.3.0", 68 | "@typescript-eslint/eslint-plugin": "^5.62.0", 69 | "@typescript-eslint/parser": "^5.62.0", 70 | "babel-jest": "^29.5.0", 71 | "babel-polyfill": "^6.26.0", 72 | "eslint": "^8.57.0", 73 | "eslint-plugin-react": "^7.34.3", 74 | "jest": "^26.6.3", 75 | "lodash.shuffle": "^4.2.0", 76 | "microbundle": "0.15.1", 77 | "normalize.css": "^8.0.1", 78 | "npm": "^10.8.1", 79 | "npm-run-all": "^4.1.5", 80 | "npm-watch": "^0.13.0", 81 | "parcel": "^1.12.4", 82 | "prettier": "^2.8.8", 83 | "react": "^18.3.1", 84 | "react-dom": "^18.3.1", 85 | "react-icons": "^3.11.0", 86 | "react-scripts": "^5.0.1", 87 | "react-test-renderer": "^18.3.1", 88 | "sinon": "^9.2.4", 89 | "styled-components": "^4.4.1", 90 | "ts-jest": "^26.5.6", 91 | "typescript": "^5.5.3", 92 | "watch": "^1.0.2" 93 | }, 94 | "files": [ 95 | "lib" 96 | ], 97 | "browserslist": [ 98 | "> 1%", 99 | "last 2 versions" 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/src/ExitContainer/index.tsx: -------------------------------------------------------------------------------- 1 | import { cloneElement, ReactElement, ReactNode } from 'react' 2 | import { constants } from 'flip-toolkit' 3 | 4 | const ExitContainer = ({ children }: { children: ReactNode }) => { 5 | return cloneElement(children as ReactElement, { 6 | [constants.DATA_EXIT_CONTAINER]: true 7 | }) 8 | } 9 | 10 | export default ExitContainer 11 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/src/Flipped/Flipped.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flipped } from './index' 3 | import TestRenderer from 'react-test-renderer' 4 | 5 | describe('Flipped Component', () => { 6 | it('adds a data-flip-id attribute', () => { 7 | const testRenderer = TestRenderer.create( 8 | 9 |
    10 | 11 | ) 12 | expect(testRenderer.toJSON()!.props['data-flip-id']).toEqual('foo') 13 | }) 14 | it('adds a data-inverse-flip-id attribute', () => { 15 | const testRenderer = TestRenderer.create( 16 | 17 |
    18 | 19 | ) 20 | expect(testRenderer.toJSON()!.props['data-inverse-flip-id']).toEqual('foo2') 21 | }) 22 | 23 | it('only if provided with a portalKey via context, puts that on the attributes to help with document scoped selection ', () => { 24 | const testRenderer = TestRenderer.create( 25 | 26 |
    27 | 28 | ) 29 | expect(testRenderer.toJSON()!.props['data-portal-key']).toBe('plants') 30 | 31 | const testRenderer2 = TestRenderer.create( 32 | 33 |
    34 | 35 | ) 36 | expect(testRenderer2.toJSON()!.props['data-flip-portal-key']).toBe( 37 | undefined 38 | ) 39 | }) 40 | it('adds all data transform attributes if none are specified', () => { 41 | const testRenderer = TestRenderer.create( 42 | 43 |
    44 | 45 | ) 46 | 47 | const flipConfig = JSON.parse( 48 | testRenderer.toJSON()!.props['data-flip-config'] 49 | ) 50 | 51 | expect(flipConfig).toEqual({ translate: true, scale: true, opacity: true }) 52 | }) 53 | 54 | it("doesn't add any additional transform attributes if at least one was specified", () => { 55 | const testRenderer2 = TestRenderer.create( 56 | 57 |
    58 | 59 | ) 60 | 61 | const flipConfig2 = JSON.parse( 62 | testRenderer2.toJSON()!.props['data-flip-config'] 63 | ) 64 | 65 | expect(flipConfig2).toEqual({ opacity: true }) 66 | }) 67 | 68 | it('adds scale transforms', () => { 69 | const testRenderer = TestRenderer.create( 70 | 71 |
    72 | 73 | ) 74 | const flipConfig = JSON.parse( 75 | testRenderer.toJSON()!.props['data-flip-config'] 76 | ) 77 | 78 | expect(flipConfig).toEqual({ scale: true }) 79 | }) 80 | 81 | it('adds translate transforms', () => { 82 | const testRenderer = TestRenderer.create( 83 | 84 |
    85 | 86 | ) 87 | 88 | const flipConfig = JSON.parse( 89 | testRenderer.toJSON()!.props['data-flip-config'] 90 | ) 91 | 92 | expect(flipConfig).toEqual({ translate: true }) 93 | }) 94 | 95 | it('passes props if it receives a function as a child', () => { 96 | const testRenderer = TestRenderer.create( 97 | 98 | {(flippedProps: any) =>
    } 99 | 100 | ) 101 | expect(testRenderer.toJSON()!.props).toEqual({ 102 | 'data-flip-id': 'foo', 103 | 'data-flip-config': JSON.stringify({ 104 | translate: true, 105 | scale: true, 106 | opacity: true 107 | }) 108 | }) 109 | }) 110 | 111 | it('passes props if it receives a function as a child take 2', () => { 112 | const testRenderer = TestRenderer.create( 113 | 114 | {(flippedProps: any) =>
    } 115 | 116 | ) 117 | expect(testRenderer.toJSON()!.props).toEqual({ 118 | 'data-inverse-flip-id': 'bar', 119 | 'data-flip-config': JSON.stringify({ 120 | translate: true, 121 | scale: true, 122 | opacity: true 123 | }) 124 | }) 125 | }) 126 | }) 127 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/src/Flipped/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | FunctionComponent, 3 | Children, 4 | cloneElement, 5 | ReactElement 6 | } from 'react' 7 | import PropTypes from 'prop-types' 8 | import { utilities, constants } from 'flip-toolkit' 9 | import { FlippedProps, SerializableFlippedProps } from 'flip-toolkit/lib/types' 10 | import { FlipContext, PortalContext } from '../Flipper/context' 11 | 12 | function isFunction(child: any): child is Function { 13 | return typeof child === 'function' 14 | } 15 | 16 | // This wrapper creates child components for the main Flipper component 17 | export const Flipped: FunctionComponent = ({ 18 | children, 19 | flipId, 20 | inverseFlipId, 21 | portalKey, 22 | ...rest 23 | }) => { 24 | let child = children 25 | const isFunctionAsChildren = isFunction(child) 26 | 27 | if (!isFunctionAsChildren) { 28 | try { 29 | child = Children.only(children) 30 | } catch (e) { 31 | throw new Error('Each Flipped component must wrap a single child') 32 | } 33 | } 34 | 35 | // if nothing is being animated, assume everything is being animated 36 | if (!rest.scale && !rest.translate && !rest.opacity) { 37 | utilities.assign(rest, { 38 | translate: true, 39 | scale: true, 40 | opacity: true 41 | }) 42 | } 43 | 44 | const dataAttributes: Record = { 45 | [constants.DATA_FLIP_CONFIG]: JSON.stringify(rest) 46 | } 47 | 48 | if (flipId !== undefined) 49 | dataAttributes[constants.DATA_FLIP_ID] = String(flipId) 50 | else if (inverseFlipId) 51 | dataAttributes[constants.DATA_INVERSE_FLIP_ID] = String(inverseFlipId) 52 | if (portalKey !== undefined) { 53 | dataAttributes[constants.DATA_PORTAL_KEY] = portalKey 54 | } 55 | if (isFunctionAsChildren) { 56 | return (child as Function)(dataAttributes) 57 | } 58 | return cloneElement(child as ReactElement, dataAttributes) 59 | } 60 | // @ts-ignore 61 | export const FlippedWithContext: FunctionComponent = ({ 62 | children, 63 | flipId, 64 | shouldFlip, 65 | shouldInvert, 66 | onAppear, 67 | onStart, 68 | onStartImmediate, 69 | onComplete, 70 | onExit, 71 | onSpringUpdate, 72 | ...rest 73 | }) => { 74 | if (!children) { 75 | return null 76 | } 77 | if (rest.inverseFlipId) { 78 | return {children} 79 | } 80 | 81 | return ( 82 | 83 | {portalKey => ( 84 | 85 | {data => { 86 | // if there is no surrounding Flipper component, 87 | // we don't want to throw an error, so check 88 | // that data exists and is not the default string 89 | if (utilities.isObject(data) && flipId) { 90 | data[flipId] = { 91 | shouldFlip, 92 | shouldInvert, 93 | onAppear, 94 | onStart, 95 | onStartImmediate, 96 | onComplete, 97 | onExit, 98 | onSpringUpdate 99 | } 100 | } 101 | return ( 102 | 103 | {children} 104 | 105 | ) 106 | }} 107 | 108 | )} 109 | 110 | ) 111 | } 112 | if (process.env.NODE_ENV !== 'production') { 113 | FlippedWithContext.propTypes = { 114 | // @ts-expect-error 115 | children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, 116 | inverseFlipId: PropTypes.string, 117 | flipId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 118 | opacity: PropTypes.bool, 119 | translate: PropTypes.bool, 120 | scale: PropTypes.bool, 121 | transformOrigin: PropTypes.string, 122 | spring: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), 123 | onStart: PropTypes.func, 124 | onStartImmediate: PropTypes.func, 125 | onComplete: PropTypes.func, 126 | onAppear: PropTypes.func, 127 | onSpringUpdate: PropTypes.func, 128 | shouldFlip: PropTypes.func, 129 | shouldInvert: PropTypes.func, 130 | onExit: PropTypes.func, 131 | portalKey: PropTypes.string, 132 | stagger: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]) 133 | } 134 | } 135 | 136 | FlippedWithContext.displayName = 'Flipped' 137 | 138 | export default FlippedWithContext 139 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/src/Flipper/Flipper.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TestRenderer from 'react-test-renderer' 3 | import Flipper from './index' 4 | 5 | describe('Flipper Component', () => { 6 | it('allows you to customize the rendered component', () => { 7 | // @ts-ignore 8 | const testRenderer = TestRenderer.create() 9 | expect(testRenderer.toJSON()!.type).toEqual('ul') 10 | }) 11 | it('renders a div by default', () => { 12 | // @ts-ignore 13 | const testRenderer = TestRenderer.create() 14 | expect(testRenderer.toJSON()!.type).toEqual('div') 15 | }) 16 | it('allows you to pass in a className string prop', () => { 17 | // @ts-ignore 18 | const testRenderer = TestRenderer.create() 19 | expect(testRenderer.toJSON()!.props.className).toEqual('foo-bar') 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/src/Flipper/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | import { FlipCallbacks } from 'flip-toolkit/lib/types' 3 | 4 | export const FlipContext = createContext({} as FlipCallbacks) 5 | export const PortalContext = createContext('portal') 6 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/src/Flipper/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { 4 | getFlippedElementPositionsBeforeUpdate, 5 | onFlipKeyUpdate 6 | } from 'flip-toolkit' 7 | import { 8 | FlipperProps, 9 | InProgressAnimations, 10 | FlipCallbacks 11 | } from 'flip-toolkit/lib/types' 12 | import { FlippedElementPositionsBeforeUpdateReturnVals } from 'flip-toolkit/lib/flip/getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate/types' 13 | import { FlipContext, PortalContext } from './context' 14 | 15 | class Flipper extends Component { 16 | static defaultProps = { 17 | applyTransformOrigin: true, 18 | element: 'div' 19 | } 20 | 21 | private inProgressAnimations: InProgressAnimations = {} 22 | private flipCallbacks: FlipCallbacks = {} 23 | private el?: HTMLElement = undefined 24 | 25 | getSnapshotBeforeUpdate(prevProps: FlipperProps) { 26 | if (prevProps.flipKey !== this.props.flipKey && this.el) { 27 | return getFlippedElementPositionsBeforeUpdate({ 28 | element: this.el, 29 | // if onExit callbacks exist here, we'll cache the DOM node 30 | flipCallbacks: this.flipCallbacks, 31 | inProgressAnimations: this.inProgressAnimations, 32 | portalKey: this.props.portalKey 33 | }) 34 | } 35 | return null 36 | } 37 | 38 | componentDidUpdate( 39 | prevProps: FlipperProps, 40 | _prevState: any, 41 | cachedData: FlippedElementPositionsBeforeUpdateReturnVals 42 | ) { 43 | if (this.props.flipKey !== prevProps.flipKey && this.el) { 44 | onFlipKeyUpdate({ 45 | flippedElementPositionsBeforeUpdate: cachedData.flippedElementPositions, 46 | cachedOrderedFlipIds: cachedData.cachedOrderedFlipIds, 47 | containerEl: this.el, 48 | inProgressAnimations: this.inProgressAnimations, 49 | flipCallbacks: this.flipCallbacks, 50 | applyTransformOrigin: this.props.applyTransformOrigin, 51 | spring: this.props.spring, 52 | debug: this.props.debug, 53 | portalKey: this.props.portalKey, 54 | staggerConfig: this.props.staggerConfig, 55 | handleEnterUpdateDelete: this.props.handleEnterUpdateDelete, 56 | decisionData: { 57 | previous: prevProps.decisionData, 58 | current: this.props.decisionData 59 | }, 60 | onComplete: this.props.onComplete, 61 | onStart: this.props.onStart 62 | }) 63 | } 64 | } 65 | 66 | public render() { 67 | const { element, className, portalKey } = this.props 68 | const Element = element 69 | 70 | let flipperMarkup = ( 71 | 72 | {/* 73 | // @ts-ignore */} 74 | (this.el = el)} 77 | > 78 | {this.props.children} 79 | 80 | 81 | ) 82 | 83 | if (portalKey) { 84 | flipperMarkup = ( 85 | 86 | {flipperMarkup} 87 | 88 | ) 89 | } 90 | 91 | return flipperMarkup 92 | } 93 | } 94 | // @ts-ignore 95 | 96 | if (process.env.NODE_ENV !== 'production') { 97 | // @ts-ignore 98 | Flipper.propTypes = { 99 | flipKey: PropTypes.oneOfType([ 100 | PropTypes.string, 101 | PropTypes.number, 102 | PropTypes.bool 103 | ]), 104 | children: PropTypes.node.isRequired, 105 | spring: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), 106 | applyTransformOrigin: PropTypes.bool, 107 | debug: PropTypes.bool, 108 | element: PropTypes.string, 109 | className: PropTypes.string, 110 | portalKey: PropTypes.string, 111 | staggerConfig: PropTypes.object, 112 | decisionData: PropTypes.any, 113 | handleEnterUpdateDelete: PropTypes.func, 114 | onComplete: PropTypes.func, 115 | onStart: PropTypes.func 116 | } 117 | } 118 | 119 | export default Flipper 120 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/src/Spring/index.ts: -------------------------------------------------------------------------------- 1 | import { spring } from 'flip-toolkit' 2 | export default spring 3 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/src/index.ts: -------------------------------------------------------------------------------- 1 | export { disableFlip, enableFlip, isFlipEnabled } from 'flip-toolkit' 2 | export { default as Flipper } from './Flipper' 3 | export { default as Flipped } from './Flipped' 4 | export { default as ExitContainer } from './ExitContainer' 5 | export { default as spring } from './Spring' 6 | -------------------------------------------------------------------------------- /packages/react-flip-toolkit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": ["es6", "dom", "es2016", "es2017"], 6 | "sourceMap": true, 7 | "allowJs": true, 8 | "jsx": "react", 9 | "moduleResolution": "node", 10 | "forceConsistentCasingInFileNames": true, 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": true, 13 | "allowSyntheticDefaultImports": true, 14 | "skipLibCheck": true, 15 | "esModuleInterop": true, 16 | "strict": true, 17 | "resolveJsonModule": true, 18 | "isolatedModules": true 19 | }, 20 | "include": ["src", "@types"], 21 | "exclude": [ 22 | "node_modules", 23 | "build", 24 | "dist", 25 | "example", 26 | "src/**/*.test.ts", 27 | "src/**/*.test.tsx", 28 | "__tests__" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------