diff --git a/web/e.cash/components/navbar/styles.js b/web/e.cash/components/navbar/styles.js --- a/web/e.cash/components/navbar/styles.js +++ b/web/e.cash/components/navbar/styles.js @@ -2,8 +2,16 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. import styled from 'styled-components'; - -export const NavbarOuter = styled.div` +import { motion } from 'framer-motion'; +import { getAnimationSettings } from '/styles/framer-motion'; + +export const NavbarOuter = styled(motion.div).attrs(() => + getAnimationSettings({ + delay: 1, + animateUp: false, + onScroll: false, + }), +)` position: fixed; top: 0; width: 100%; diff --git a/web/e.cash/package-lock.json b/web/e.cash/package-lock.json --- a/web/e.cash/package-lock.json +++ b/web/e.cash/package-lock.json @@ -8,6 +8,7 @@ "name": "e.cash", "version": "0.0.1", "dependencies": { + "framer-motion": "^10.12.18", "lottie-react": "^2.4.0", "next": "latest", "react": "18.2.0", @@ -4554,6 +4555,44 @@ "node": ">= 6" } }, + "node_modules/framer-motion": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.12.18.tgz", + "integrity": "sha512-cfhiUpPbj+0eEWKjuD+5cz5cMqH71xOtMxGiS/cSGfHn2OlHIEAqFnFyzEMENw5PxWR9bMVhatzzpD6lexmHZQ==", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -12879,6 +12918,32 @@ "mime-types": "^2.1.12" } }, + "framer-motion": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.12.18.tgz", + "integrity": "sha512-cfhiUpPbj+0eEWKjuD+5cz5cMqH71xOtMxGiS/cSGfHn2OlHIEAqFnFyzEMENw5PxWR9bMVhatzzpD6lexmHZQ==", + "requires": { + "@emotion/is-prop-valid": "^0.8.2", + "tslib": "^2.4.0" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + } + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", diff --git a/web/e.cash/package.json b/web/e.cash/package.json --- a/web/e.cash/package.json +++ b/web/e.cash/package.json @@ -9,6 +9,7 @@ "test": "jest --watch" }, "dependencies": { + "framer-motion": "^10.12.18", "lottie-react": "^2.4.0", "next": "latest", "react": "18.2.0", diff --git a/web/e.cash/styles/framer-motion/__tests__/getAnimationSettings.test.js b/web/e.cash/styles/framer-motion/__tests__/getAnimationSettings.test.js new file mode 100644 --- /dev/null +++ b/web/e.cash/styles/framer-motion/__tests__/getAnimationSettings.test.js @@ -0,0 +1,59 @@ +// Copyright (c) 2023 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +import { getAnimationSettings } from '../index.js'; + +describe('getAnimationSettings', () => { + it('should return an animationSettings object with the default values if it is called with no parameters', () => { + const animationSettings = { + initial: { opacity: 0, y: 200 }, + animate: {}, + whileInView: { opacity: 1, y: 0 }, + transition: { + delay: 0, + duration: 1, + type: 'spring', + }, + viewport: { once: true }, + }; + const result = getAnimationSettings(); + expect(result).toEqual(animationSettings); + }); + + it('should return an animationSettings object with any specified object property values that are passed', () => { + const animationSettings = { + initial: { opacity: 0, y: -100 }, + animate: { opacity: 1, y: 0 }, + whileInView: null, + transition: { + delay: 2, + duration: 2, + type: 'spring', + }, + viewport: { once: true }, + }; + const result = getAnimationSettings({ + duration: 2, + delay: 2, + animateUp: false, + onScroll: false, + displacement: 100, + }); + expect(result).toEqual(animationSettings); + }); + + it('should throw an error if an invalid property type is passed', () => { + expect(() => { + getAnimationSettings({ + duration: 'notanumber', + }); + }).toThrow('Invalid animation value'); + + expect(() => { + getAnimationSettings({ + duration: 1, + animateUp: 'notaboolean', + }); + }).toThrow('Invalid animation value'); + }); +}); diff --git a/web/e.cash/styles/framer-motion/index.js b/web/e.cash/styles/framer-motion/index.js new file mode 100644 --- /dev/null +++ b/web/e.cash/styles/framer-motion/index.js @@ -0,0 +1,43 @@ +// Copyright (c) 2023 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +/** + * + * @param {object} - an options object that contains various animation parameters + * The default values will be used if none are passed + * @returns {object} animationSettings - an object with the specified framer motion animation settings + * @throws {error} if incorrect value types are passed + */ +export const getAnimationSettings = ({ + duration = 1, + delay = 0, + animateUp = true, + onScroll = true, + displacement = 200, +} = {}) => { + if ( + typeof duration !== 'number' || + typeof delay !== 'number' || + typeof animateUp !== 'boolean' || + typeof onScroll !== 'boolean' || + typeof displacement !== 'number' + ) { + throw new Error('Invalid animation value'); + } + const animationSettings = { + initial: { + opacity: 0, + y: animateUp ? displacement : -displacement, + }, + animate: onScroll ? {} : { opacity: 1, y: 0 }, + whileInView: onScroll ? { opacity: 1, y: 0 } : null, + transition: { + delay, + duration, + type: 'spring', + }, + viewport: { once: true }, + }; + + return animationSettings; +}; diff --git a/web/e.cash/styles/pages/homepage.js b/web/e.cash/styles/pages/homepage.js --- a/web/e.cash/styles/pages/homepage.js +++ b/web/e.cash/styles/pages/homepage.js @@ -3,8 +3,12 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. import styled from 'styled-components'; import Neoncity from '/public/images/neon-city.png'; +import { motion } from 'framer-motion'; +import { getAnimationSettings } from '/styles/framer-motion'; -export const Hero = styled.div` +export const Hero = styled(motion.div).attrs(() => + getAnimationSettings({ duration: 2, delay: 0.4, displacement: 300 }), +)` width: 100%; height: 100vh; min-height: 600px; @@ -168,7 +172,9 @@ ); `; -export const StorySection = styled.div` +export const StorySection = styled(motion.div).attrs(() => + getAnimationSettings(), +)` display: flex; gap: 30px; position: relative; @@ -286,7 +292,9 @@ position: relative; `; -export const TilesSectionCtn = styled.div` +export const TilesSectionCtn = styled(motion.div).attrs(() => + getAnimationSettings(), +)` margin-top: 200px; text-align: center; `;