├── .DS_Store ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── build ├── _redirects ├── asset-manifest.json ├── favicon.png ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── robots.txt └── static │ ├── css │ ├── main.fec48c59.css │ └── main.fec48c59.css.map │ └── js │ ├── main.5f22276f.js │ ├── main.5f22276f.js.LICENSE.txt │ └── main.5f22276f.js.map ├── package-lock.json ├── package.json ├── public ├── _redirects ├── favicon.png ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.tsx ├── index.css ├── index.tsx ├── library │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── BubblyBubbles.tsx │ │ ├── BubblyContainer.tsx │ │ ├── BubblyLink.tsx │ │ ├── index.d.ts │ │ ├── index.ts │ │ └── styles.css ├── react-app-env.d.ts ├── routes │ ├── About.tsx │ ├── Contact.tsx │ └── Docs.tsx └── shared │ ├── Nav.tsx │ └── Title.tsx └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/079ba7c95983a3c385fdcc2d36e75d71419cfbe0/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-bubbly-transitions 2 | 3 | Show bubbly transitions between route changes, in your React 18 apps. 4 | 5 | Click [here for a demo](https://bubbles.frontendjoe.com/). 6 | 7 | Or [check out the npm package here](https://www.npmjs.com/package/react-bubbly-transitions). 8 | 9 | ## Installation 10 | 11 | Just a few quick steps to get started: 12 | 13 | #### 1. Create a React app (optional) 14 | 15 | If you are adding the transitions to an existing app, you can skip this step. 16 | 17 | ```sh 18 | npx create-react-app my-bubbly-app 19 | cd my-bubbly-app 20 | ``` 21 | 22 | #### 2. Install dependencies 23 | 24 | Our project depends upon React's router library 25 | 26 | ```sh 27 | npm i react-bubbly-transitions react-router-dom 28 | ``` 29 | 30 | #### 3. Add components 31 | 32 | The package relies on two components being present. 33 | 34 | ##### BubblyContainer 35 | 36 | This is what houses our wave transition between route changes and does not require any props. 37 | 38 | ##### BubblyLink 39 | 40 | This button can be declared anywhere inside your Router component. 41 | 42 | It takes the following props: 43 | 44 | | Prop | Description | Example | type | required | default | 45 | | ---------- | -------------------------------------------------------------------------------------------- | ------- | ------------------ | -------- | ------- | 46 | | children | The content inside the link | About | String / Component | true | | 47 | | to | The route that the link will take you to | /about | String | true | | 48 | | colorStart | The background color of the bubble shape that appears first. Must be a hexcode or rgba value | #8f44fd | String | false | #8f44fd | 49 | | colorEnd | The background color of the bubble shape that appears last. Must be a hexcode or rgba value | #ffffff | String | false | #ffffff | 50 | 51 | Be careful with the duration (too fast/slow can ruin the effect) - my recommended duration is between 1000ms and 1600ms. 52 | 53 | ##### Example App.tsx 54 | 55 | Copy this whole code snippet into your App.tsx for a basic example: 56 | 57 | ```typescript 58 | import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"; 59 | 60 | import { BubblyContainer, BubblyLink } from "react-bubbly-transitions"; 61 | 62 | const Home = () =>
Home
; 63 | const About = () =>
About
; 64 | const Contact = () =>
Contact
; 65 | 66 | function App() { 67 | return ( 68 | 69 | 70 | 71 | 75 | Home 76 | About 77 | Contact 78 | 79 | 80 | } 81 | > 82 | } /> 83 | } /> 84 | } /> 85 | No Match} /> 86 | 87 | 88 | 89 | ); 90 | } 91 | 92 | export default App; 93 | ``` 94 | 95 | ### 4. Styling 96 | 97 | To style the BubblyLink component you can target it via css (just be more specific than me 😄): 98 | 99 | ```css 100 | body .react-bubbly-transitions__bubbly-link { 101 | color: #af44fd; 102 | } 103 | ``` 104 | 105 | To style the active state of the BubblyLink, just target the .active class and again be more specific. 106 | 107 | ```css 108 | body .react-bubbly-transitions__bubbly-link.active { 109 | text-decoration: underline; 110 | } 111 | ``` 112 | 113 | ### 5. DRY (Don't Repeat Yourself) 114 | 115 | To avoid repeating certain BubblyLink props, I recommend creating your own generic link component that sets the props here by default. 116 | 117 | ```typescript 118 | import { FC, ReactNode } from "react"; 119 | import { BubblyLink } from "react-bubbly-transitions"; 120 | 121 | type Props = { 122 | to: string; 123 | children: ReactNode; 124 | }; 125 | 126 | export const MyBubblyLink: FC = ({ to, children }) => ( 127 | 128 | {children} 129 | 130 | ); 131 | ``` 132 | 133 | ### 6. Have fun with it! 134 | 135 | Please hit me up on [My Instagram page](https://instagram.com/frontendjoe) for any support or suggestions 🙂 136 | -------------------------------------------------------------------------------- /build/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.fec48c59.css", 4 | "main.js": "/static/js/main.5f22276f.js", 5 | "index.html": "/index.html", 6 | "main.fec48c59.css.map": "/static/css/main.fec48c59.css.map", 7 | "main.5f22276f.js.map": "/static/js/main.5f22276f.js.map" 8 | }, 9 | "entrypoints": [ 10 | "static/css/main.fec48c59.css", 11 | "static/js/main.5f22276f.js" 12 | ] 13 | } -------------------------------------------------------------------------------- /build/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/079ba7c95983a3c385fdcc2d36e75d71419cfbe0/build/favicon.png -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | react-wavy-transitions
-------------------------------------------------------------------------------- /build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/079ba7c95983a3c385fdcc2d36e75d71419cfbe0/build/logo192.png -------------------------------------------------------------------------------- /build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/079ba7c95983a3c385fdcc2d36e75d71419cfbe0/build/logo512.png -------------------------------------------------------------------------------- /build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-wavy-transitions", 3 | "name": "react-wavy-transitions", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "favicon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "favicon.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /build/static/css/main.fec48c59.css: -------------------------------------------------------------------------------- 1 | body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}nav{align-items:flex-start;background:#1d1e22;box-sizing:border-box;color:#f9f9f9;display:flex;flex-direction:column;gap:10px;height:100%;left:0;margin:0;position:fixed;top:0;width:120px;z-index:99999}main,nav{padding:20px}main{margin-left:120px}body{margin:0}.react-wavy-transitions__first,.react-wavy-transitions__second{-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;height:100%;left:0;min-width:800px;position:fixed;top:0;width:100%;z-index:9999}.down.react-wavy-transitions__first{-webkit-animation-name:down-first-move;animation-name:down-first-move;top:2px;translate:0 -200%}.down.react-wavy-transitions__second{-webkit-animation-name:down-second-move;animation-name:down-second-move;rotate:180deg;translate:0 -100%}@-webkit-keyframes down-first-move{to{translate:0 0}}@keyframes down-first-move{to{translate:0 0}}@-webkit-keyframes down-second-move{to{translate:0 100%}}@keyframes down-second-move{to{translate:0 100%}}.up.react-wavy-transitions__first{-webkit-animation-name:up-first-move;animation-name:up-first-move;rotate:180deg;translate:0 200%}.up.react-wavy-transitions__second{-webkit-animation-name:up-second-move;animation-name:up-second-move;top:2px;translate:0 100%}@-webkit-keyframes up-first-move{to{translate:0 0}}@keyframes up-first-move{to{translate:0 0}}@-webkit-keyframes up-second-move{to{translate:0 -100%}}@keyframes up-second-move{to{translate:0 -100%}}.react-wavy-transitions__wavy-link{background:transparent;border:0;color:inherit;cursor:pointer;font-family:inherit;font-size:1rem} 2 | /*# sourceMappingURL=main.fec48c59.css.map*/ -------------------------------------------------------------------------------- /build/static/css/main.fec48c59.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"static/css/main.fec48c59.css","mappings":"AAAA,KAKE,kCAAmC,CACnC,iCAAkC,CAJlC,mIAKF,CAEA,IAWE,sBAAuB,CAJvB,kBAAmB,CAQnB,qBAAsB,CAPtB,aAAc,CACd,YAAa,CACb,qBAAsB,CAEtB,QAAS,CANT,WAAY,CAFZ,MAAO,CAUP,QAAS,CAbT,cAAe,CAEf,KAAM,CAEN,WAAY,CAHZ,aAcF,CAEA,SALE,YAQF,CAHA,KACE,iBAEF,CC9BA,KACE,QACF,CAEA,+DASE,6CAAsC,CAAtC,qCAAsC,CADtC,WAAY,CAHZ,MAAO,CAEP,eAAgB,CALhB,cAAe,CAEf,KAAM,CAEN,UAAW,CAHX,YAOF,CAGA,oCAGE,sCAA+B,CAA/B,8BAA+B,CAF/B,OAAQ,CACR,iBAEF,CAEA,qCAGE,uCAAgC,CAAhC,+BAAgC,CAFhC,aAAc,CACd,iBAEF,CAEA,mCACE,GACE,aACF,CACF,CAJA,2BACE,GACE,aACF,CACF,CAEA,oCACE,GACE,gBACF,CACF,CAJA,4BACE,GACE,gBACF,CACF,CAGA,kCAGE,oCAA6B,CAA7B,4BAA6B,CAF7B,aAAc,CACd,gBAEF,CAEA,mCAGE,qCAA8B,CAA9B,6BAA8B,CAF9B,OAAQ,CACR,gBAEF,CAEA,iCACE,GACE,aACF,CACF,CAJA,yBACE,GACE,aACF,CACF,CAEA,kCACE,GACE,iBACF,CACF,CAJA,0BACE,GACE,iBACF,CACF,CAGA,mCACE,sBAAuB,CACvB,QAAS,CACT,aAAc,CAGd,cAAe,CAFf,mBAAoB,CACpB,cAEF","sources":["index.css","library/waves/styles.css"],"sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\nnav {\n position: fixed;\n z-index: 99999;\n top: 0;\n left: 0;\n width: 120px;\n height: 100%;\n background: #1d1e22;\n color: #f9f9f9;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n padding: 20px;\n margin: 0;\n box-sizing: border-box;\n}\n\nmain {\n margin-left: 120px;\n padding: 20px;\n}\n","body {\n margin: 0;\n}\n\n.react-wavy-transitions__first,\n.react-wavy-transitions__second {\n position: fixed;\n z-index: 9999;\n top: 0;\n left: 0;\n width: 100%;\n min-width: 800px;\n height: 100%;\n animation-timing-function: ease-in-out;\n}\n\n/* direction down */\n.down.react-wavy-transitions__first {\n top: 2px;\n translate: 0 -200%;\n animation-name: down-first-move;\n}\n\n.down.react-wavy-transitions__second {\n rotate: 180deg;\n translate: 0 -100%;\n animation-name: down-second-move;\n}\n\n@keyframes down-first-move {\n 100% {\n translate: 0 0;\n }\n}\n\n@keyframes down-second-move {\n 100% {\n translate: 0 100%;\n }\n}\n\n/* direction up */\n.up.react-wavy-transitions__first {\n rotate: 180deg;\n translate: 0 200%;\n animation-name: up-first-move;\n}\n\n.up.react-wavy-transitions__second {\n top: 2px;\n translate: 0 100%;\n animation-name: up-second-move;\n}\n\n@keyframes up-first-move {\n 100% {\n translate: 0 0;\n }\n}\n\n@keyframes up-second-move {\n 100% {\n translate: 0 -100%;\n }\n}\n\n/* Wavy Link */\n.react-wavy-transitions__wavy-link {\n background: transparent;\n border: 0;\n color: inherit;\n font-family: inherit;\n font-size: 1rem;\n cursor: pointer;\n}\n"],"names":[],"sourceRoot":""} -------------------------------------------------------------------------------- /build/static/js/main.5f22276f.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * ApexCharts v3.35.5 3 | * (c) 2018-2022 ApexCharts 4 | * Released under the MIT License. 5 | */ 6 | 7 | /*! svg.draggable.js - v2.2.2 - 2019-01-08 8 | * https://github.com/svgdotjs/svg.draggable.js 9 | * Copyright (c) 2019 Wout Fierens; Licensed MIT */ 10 | 11 | /*! svg.filter.js - v2.0.2 - 2016-02-24 12 | * https://github.com/wout/svg.filter.js 13 | * Copyright (c) 2016 Wout Fierens; Licensed MIT */ 14 | 15 | /** 16 | * @license React 17 | * react-dom.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** 26 | * @license React 27 | * react-jsx-runtime.production.min.js 28 | * 29 | * Copyright (c) Facebook, Inc. and its affiliates. 30 | * 31 | * This source code is licensed under the MIT license found in the 32 | * LICENSE file in the root directory of this source tree. 33 | */ 34 | 35 | /** 36 | * @license React 37 | * react.production.min.js 38 | * 39 | * Copyright (c) Facebook, Inc. and its affiliates. 40 | * 41 | * This source code is licensed under the MIT license found in the 42 | * LICENSE file in the root directory of this source tree. 43 | */ 44 | 45 | /** 46 | * @license React 47 | * scheduler.production.min.js 48 | * 49 | * Copyright (c) Facebook, Inc. and its affiliates. 50 | * 51 | * This source code is licensed under the MIT license found in the 52 | * LICENSE file in the root directory of this source tree. 53 | */ 54 | 55 | /** 56 | * @remix-run/router v1.0.2 57 | * 58 | * Copyright (c) Remix Software Inc. 59 | * 60 | * This source code is licensed under the MIT license found in the 61 | * LICENSE.md file in the root directory of this source tree. 62 | * 63 | * @license MIT 64 | */ 65 | 66 | /** 67 | * React Router v6.4.2 68 | * 69 | * Copyright (c) Remix Software Inc. 70 | * 71 | * This source code is licensed under the MIT license found in the 72 | * LICENSE.md file in the root directory of this source tree. 73 | * 74 | * @license MIT 75 | */ 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bubbly-transitions-examples", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/node": "^16.11.64", 7 | "@types/react": "^18.0.21", 8 | "@types/react-dom": "^18.0.6", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-router-dom": "^6.4.2", 12 | "react-scripts": "5.0.1", 13 | "typescript": "^4.8.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build" 18 | }, 19 | "eslintConfig": { 20 | "extends": [ 21 | "react-app" 22 | ] 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/079ba7c95983a3c385fdcc2d36e75d71419cfbe0/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | react-bubbly-transitions 28 | 29 | 30 | 34 | 35 | 36 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/079ba7c95983a3c385fdcc2d36e75d71419cfbe0/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/079ba7c95983a3c385fdcc2d36e75d71419cfbe0/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-wavy-transitions", 3 | "name": "react-wavy-transitions", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "favicon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "favicon.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"; 3 | 4 | import About from "./routes/About"; 5 | import Docs from "./routes/Docs"; 6 | import Contact from "./routes/Contact"; 7 | 8 | import { BubblyContainer } from "./library/src"; 9 | 10 | function App() { 11 | return ( 12 | 13 | 14 | 15 | }> 16 | } /> 17 | ...}> 21 | 22 | 23 | } 24 | /> 25 | ...}> 29 | 30 | 31 | } 32 | /> 33 | No Match} /> 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | export default App; 41 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 64px 15% 0; 4 | font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", 5 | "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", 6 | "Helvetica Neue", sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | header { 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | } 16 | 17 | nav { 18 | display: flex; 19 | gap: 10px; 20 | } 21 | 22 | @keyframes in-keyframes { 23 | 0% { 24 | opacity: 0; 25 | translate: 0 100%; 26 | } 27 | 100% { 28 | opacity: 1; 29 | translate: 0 0; 30 | } 31 | } 32 | 33 | .animate-in { 34 | animation-name: in-keyframes; 35 | animation-duration: 0.5s; 36 | animation-fill-mode: both; 37 | } 38 | 39 | /* wave link styles */ 40 | body .react-bubbly-transitions__bubbly-link { 41 | padding: 0; 42 | outline: none; 43 | } 44 | 45 | body .react-bubbly-transitions__bubbly-link.active { 46 | position: relative; 47 | color: #8f44fd; 48 | } 49 | 50 | body .react-bubbly-transitions__bubbly-link.active::after { 51 | content: ""; 52 | position: absolute; 53 | left: 0; 54 | bottom: 0; 55 | width: 100%; 56 | height: 2px; 57 | border-radius: 3px; 58 | background: #8f44fd; 59 | } 60 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById("root") as HTMLElement 8 | ); 9 | root.render( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/library/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /src/library/README.md: -------------------------------------------------------------------------------- 1 | # react-bubbly-transitions 2 | 3 | Show bubbly transitions between route changes, in your React 18 apps. 4 | 5 | Click [here for a demo](https://bubbles.frontendjoe.com/). 6 | 7 | Or [check out the npm package here](https://www.npmjs.com/package/react-bubbly-transitions). 8 | 9 | ## Installation 10 | 11 | Just a few quick steps to get started: 12 | 13 | #### 1. Create a React app (optional) 14 | 15 | If you are adding the transitions to an existing app, you can skip this step. 16 | 17 | ```sh 18 | npx create-react-app my-bubbly-app 19 | cd my-bubbly-app 20 | ``` 21 | 22 | #### 2. Install dependencies 23 | 24 | Our project depends upon React's router library 25 | 26 | ```sh 27 | npm i react-bubbly-transitions react-router-dom 28 | ``` 29 | 30 | #### 3. Add components 31 | 32 | The package relies on two components being present. 33 | 34 | ##### BubblyContainer 35 | 36 | This is what houses our wave transition between route changes and does not require any props. 37 | 38 | ##### BubblyLink 39 | 40 | This button can be declared anywhere inside your Router component. 41 | 42 | It takes the following props: 43 | 44 | | Prop | Description | Example | type | required | default | 45 | | ---------- | -------------------------------------------------------------------------------------------- | ------- | ------------------ | -------- | ------- | 46 | | children | The content inside the link | About | String / Component | true | | 47 | | to | The route that the link will take you to | /about | String | true | | 48 | | colorStart | The background color of the bubble shape that appears first. Must be a hexcode or rgba value | #8f44fd | String | false | #8f44fd | 49 | | colorEnd | The background color of the bubble shape that appears last. Must be a hexcode or rgba value | #ffffff | String | false | #ffffff | 50 | 51 | Be careful with the duration (too fast/slow can ruin the effect) - my recommended duration is between 1000ms and 1600ms. 52 | 53 | ##### Example App.tsx 54 | 55 | Copy this whole code snippet into your App.tsx for a basic example: 56 | 57 | ```typescript 58 | import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"; 59 | 60 | import { BubblyContainer, BubblyLink } from "react-bubbly-transitions"; 61 | 62 | const Home = () =>
Home
; 63 | const About = () =>
About
; 64 | const Contact = () =>
Contact
; 65 | 66 | function App() { 67 | return ( 68 | 69 | 70 | 71 | 75 | Home 76 | About 77 | Contact 78 | 79 | 80 | } 81 | > 82 | } /> 83 | } /> 84 | } /> 85 | No Match} /> 86 | 87 | 88 | 89 | ); 90 | } 91 | 92 | export default App; 93 | ``` 94 | 95 | ### 4. Styling 96 | 97 | To style the BubblyLink component you can target it via css (just be more specific than me 😄): 98 | 99 | ```css 100 | body .react-bubbly-transitions__bubbly-link { 101 | color: #af44fd; 102 | } 103 | ``` 104 | 105 | To style the active state of the BubblyLink, just target the .active class and again be more specific. 106 | 107 | ```css 108 | body .react-bubbly-transitions__bubbly-link.active { 109 | text-decoration: underline; 110 | } 111 | ``` 112 | 113 | ### 5. DRY (Don't Repeat Yourself) 114 | 115 | To avoid repeating certain BubblyLink props, I recommend creating your own generic link component that sets the props here by default. 116 | 117 | ```typescript 118 | import { FC, ReactNode } from "react"; 119 | import { BubblyLink } from "react-bubbly-transitions"; 120 | 121 | type Props = { 122 | to: string; 123 | children: ReactNode; 124 | }; 125 | 126 | export const MyBubblyLink: FC = ({ to, children }) => ( 127 | 128 | {children} 129 | 130 | ); 131 | ``` 132 | 133 | ### 6. Have fun with it! 134 | 135 | Please hit me up on [My Instagram page](https://instagram.com/frontendjoe) for any support or suggestions 🙂 136 | -------------------------------------------------------------------------------- /src/library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bubbly-transitions", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist/*.*" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "publish:npm": "rm -Rf dist && mkdir dist && babel --extensions .ts,.tsx src --ignore 'src/index.d.ts' -d dist --copy-files" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/frontend-joe/react-bubbly-transitions.git" 16 | }, 17 | "author": "@frontendjoe", 18 | "license": "MIT", 19 | "types": "./dist/index.d.ts", 20 | "bugs": { 21 | "url": "https://github.com/frontend-joe/react-bubbly-transitions/issues" 22 | }, 23 | "homepage": "https://github.com/frontend-joe/react-bubbly-transitions#readme", 24 | "dependencies": { 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0", 27 | "react-router-dom": "^6.4.2" 28 | }, 29 | "devDependencies": { 30 | "@babel/cli": "^7.18.10", 31 | "@babel/preset-env": "^7.19.1", 32 | "@babel/preset-react": "^7.18.6", 33 | "@babel/preset-typescript": "^7.18.6" 34 | }, 35 | "babel": { 36 | "presets": [ 37 | [ 38 | "@babel/react", 39 | { 40 | "runtime": "automatic" 41 | } 42 | ], 43 | [ 44 | "@babel/preset-typescript" 45 | ], 46 | [ 47 | "@babel/preset-env" 48 | ] 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/library/src/BubblyBubbles.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import "./styles.css"; 3 | 4 | type Props = { 5 | colorStart: string; 6 | colorEnd: string; 7 | duration: number; 8 | }; 9 | 10 | export const BubblyBubbles: FC = ({ 11 | colorStart, 12 | colorEnd, 13 | duration, 14 | }) => { 15 | return ( 16 |
17 |
21 |
25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/library/src/BubblyContainer.tsx: -------------------------------------------------------------------------------- 1 | export const BubblyContainer = () => ( 2 |
3 | ); 4 | -------------------------------------------------------------------------------- /src/library/src/BubblyLink.tsx: -------------------------------------------------------------------------------- 1 | import { FC, MouseEvent, ReactNode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { BubblyBubbles } from "./BubblyBubbles"; 5 | 6 | export type BubblyLinkProps = { 7 | to: string; 8 | children: ReactNode; 9 | colorStart?: string; 10 | colorEnd?: string; 11 | duration?: number; 12 | }; 13 | 14 | export const BubblyLink: FC = ({ 15 | to, 16 | children, 17 | colorStart = "#8f44fd", 18 | colorEnd = "#ffffff", 19 | duration = 1250, 20 | }) => { 21 | const navigate = useNavigate(); 22 | 23 | const handleClick = (e: MouseEvent | undefined) => { 24 | e?.preventDefault(); 25 | 26 | if ( 27 | !document.getElementById("react-bubbly-transitions__bubbles") && 28 | window.location.pathname !== to 29 | ) { 30 | // change the url in address bar 31 | window.history.pushState("", "", to); 32 | 33 | // get access to wave container 34 | const container = createRoot( 35 | document.getElementById("react-bubbly-transitions__container")! 36 | ); 37 | 38 | // show the waves 39 | container.render( 40 | 45 | ); 46 | 47 | // do the route change 48 | setTimeout(() => navigate(to), duration / 2); // half total animation 49 | 50 | // hide the waves 51 | setTimeout(() => container.unmount(), duration); // total animation 52 | } 53 | }; 54 | 55 | return ( 56 | 65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /src/library/src/index.d.ts: -------------------------------------------------------------------------------- 1 | export { BubblyContainer } from "./BubblyContainer"; 2 | export { BubblyLink, BubblyLinkProps } from "./BubblyLink"; 3 | -------------------------------------------------------------------------------- /src/library/src/index.ts: -------------------------------------------------------------------------------- 1 | export { BubblyContainer } from "./BubblyContainer"; 2 | export { BubblyLink } from "./BubblyLink"; 3 | -------------------------------------------------------------------------------- /src/library/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | #react-bubbly-transitions__bubbles { 6 | --size: 200vw; 7 | } 8 | 9 | @media only screen and (min-width: 768px) { 10 | #react-bubbly-transitions__bubbles { 11 | --size: 125vw; 12 | } 13 | } 14 | 15 | .react-bubbly-transitions__first, 16 | .react-bubbly-transitions__second { 17 | position: fixed; 18 | z-index: 9999; 19 | top: 0; 20 | left: 50%; 21 | translate: -50% 100%; 22 | width: var(--size); 23 | height: var(--size); 24 | border-radius: var(--size); 25 | animation-timing-function: ease-in-out; 26 | } 27 | 28 | .react-bubbly-transitions__first { 29 | animation-name: bubble-move; 30 | } 31 | 32 | .react-bubbly-transitions__second { 33 | animation-name: bubble-second-move; 34 | } 35 | 36 | @keyframes bubble-move { 37 | 20% { 38 | border-radius: var(--size); 39 | } 40 | 50%, 41 | 100% { 42 | translate: -50% 0; 43 | border-radius: 0; 44 | } 45 | } 46 | 47 | @keyframes bubble-second-move { 48 | 30% { 49 | translate: -50% 100%; 50 | } 51 | 50% { 52 | border-radius: var(--size); 53 | } 54 | 100% { 55 | translate: -50% 0; 56 | border-radius: 0; 57 | } 58 | } 59 | 60 | /* bubbly Link */ 61 | .react-bubbly-transitions__bubbly-link { 62 | background: transparent; 63 | border: 0; 64 | color: inherit; 65 | font-family: inherit; 66 | font-size: 1rem; 67 | cursor: pointer; 68 | } 69 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/routes/About.tsx: -------------------------------------------------------------------------------- 1 | import { Nav } from "../shared/Nav"; 2 | import { Title } from "../shared/Title"; 3 | 4 | const Wrapper = () => ( 5 | <> 6 |
7 | About 8 |
10 | 11 | ); 12 | 13 | export default Wrapper; 14 | -------------------------------------------------------------------------------- /src/routes/Contact.tsx: -------------------------------------------------------------------------------- 1 | import { Nav } from "../shared/Nav"; 2 | import { Title } from "../shared/Title"; 3 | 4 | const Wrapper = () => ( 5 | <> 6 |
7 | Contact 8 |
10 | 11 | ); 12 | 13 | export default Wrapper; 14 | -------------------------------------------------------------------------------- /src/routes/Docs.tsx: -------------------------------------------------------------------------------- 1 | import { Nav } from "../shared/Nav"; 2 | import { Title } from "../shared/Title"; 3 | 4 | const Wrapper = () => ( 5 | <> 6 |
7 | Docs 8 |
10 | 11 | ); 12 | 13 | export default Wrapper; 14 | -------------------------------------------------------------------------------- /src/shared/Nav.tsx: -------------------------------------------------------------------------------- 1 | import { BubblyLink } from "../library/src"; 2 | 3 | const MyBubblyLink = ({ to = "", text = "" }) => ( 4 | 5 | {text} 6 | 7 | ); 8 | 9 | export const Nav = () => ( 10 | 15 | ); 16 | -------------------------------------------------------------------------------- /src/shared/Title.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | 3 | export const Title: FC = ({ children }) => ( 4 |

5 | {children} 6 |

7 | ); 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------