├── .gitignore ├── main ├── src │ ├── vite-env.d.ts │ ├── react-waves-effect │ │ ├── ripple.css │ │ └── index.tsx │ ├── main.tsx │ └── App.tsx ├── .gitignore ├── .prettierrc ├── index.html ├── tsconfig.json ├── package.json └── vite.config.ts ├── assets └── logo.jpg ├── .npmignore ├── docs ├── overrides │ └── main.html ├── playground │ └── index.md ├── css │ ├── custom.css │ └── termynal.css ├── index.md └── js │ ├── custom.js │ └── termynal.js ├── .github └── workflows │ └── cd.yml ├── README.md ├── LICENSE ├── package.json ├── mkdocs.yml └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | yarn.lock -------------------------------------------------------------------------------- /main/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /main/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeulAria/react-waves-effect/HEAD/assets/logo.jpg -------------------------------------------------------------------------------- /main/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 85, 3 | "arrowParens": "always", 4 | "semi": true, 5 | "tabWidth": 2, 6 | "bracketSpacing": false, 7 | "trailingComma": "es5" 8 | } 9 | -------------------------------------------------------------------------------- /main/src/react-waves-effect/ripple.css: -------------------------------------------------------------------------------- 1 | @keyframes wave-animate { 2 | 0% { 3 | width: 0px; 4 | height: 0px; 5 | opacity: 0.5; 6 | } 7 | 100% { 8 | opacity: 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | assets 3 | docs 4 | main 5 | node_modules 6 | .git 7 | .gitignore 8 | tsconfig.json 9 | yarn.lock 10 | package-lock.json 11 | LICENSE 12 | mkdocs.yml 13 | README.md 14 | tsconfig.json 15 | yarn.lock -------------------------------------------------------------------------------- /main/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ) 11 | -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {{ super() }} 5 | 6 | {% if git_page_authors %} 7 |
8 | 9 | {{ git_page_authors | default('enable mkdocs-git-authors-plugin') }} 10 | 11 |
12 | {% endif %} 13 | {% endblock %} -------------------------------------------------------------------------------- /main/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: deploy to ghpages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-python@v2 13 | with: 14 | python-version: 3.x 15 | - run: pip install mkdocs-material 16 | - run: pip install mkdocs-macros-plugin 17 | - run: mkdocs gh-deploy --force -------------------------------------------------------------------------------- /docs/playground/index.md: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /main/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "allowJs": false, 6 | "skipLibCheck": false, 7 | "esModuleInterop": false, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "ESNext", 12 | "moduleResolution": "Node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | }, 18 | "include": ["./src"] 19 | } 20 | -------------------------------------------------------------------------------- /main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "tsc && vite build", 6 | "serve": "vite preview" 7 | }, 8 | "dependencies": { 9 | "react": "^17.0.0", 10 | "react-dom": "^17.0.0", 11 | "react-waves-effect": "^1.4.0" 12 | }, 13 | "devDependencies": { 14 | "@originjs/vite-plugin-commonjs": "^1.0.2", 15 | "@types/react": "^17.0.0", 16 | "@types/react-dom": "^17.0.0", 17 | "@vitejs/plugin-react": "^1.1.4", 18 | "@vitejs/plugin-react-refresh": "^1.3.1", 19 | "path": "^0.12.7", 20 | "typescript": "^4.3.2", 21 | "vite": "^2.7.13", 22 | "vite-plugin-eslint": "^1.3.0", 23 | "vite-tsconfig-paths": "^3.3.17" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/css/custom.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap"); 2 | 3 | :root { 4 | --md-text-font-family: "Inter", Roboto, sans-serif; 5 | --md-code-font-family: "Space Mono", monospace; 6 | } 7 | 8 | a.external-link::after { 9 | /* \00A0 is a non-breaking space 10 | to make the mark be on the same line as the link 11 | */ 12 | content: "\00A0[↪]"; 13 | } 14 | 15 | a.internal-link::after { 16 | /* \00A0 is a non-breaking space 17 | to make the mark be on the same line as the link 18 | */ 19 | content: "\00A0↪"; 20 | } 21 | 22 | div.doc-contents:not(.first) { 23 | padding-left: 25px; 24 | border-left: 4px solid rgba(230, 230, 230); 25 | margin-bottom: 80px; 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

React Waves Effect

7 |

8 | package size 9 | npm version 10 | downloads per month 11 |

12 |

13 | 14 | 15 | 16 |

17 | 18 | 19 |

Waves effect react component library!

20 |
21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 LeulAria 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-waves-effect", 3 | "version": "1.5.0", 4 | "main": "lib/index.js", 5 | "private": false, 6 | "description": "Waves effect react component!", 7 | "types": "lib", 8 | "scripts": { 9 | "clean": "rimraf lib/", 10 | "copy-files": "copyfiles -f \"main/src/react-waves-effect/**/*.html\" \"main/src/react-waves-effect/**/*.css\" lib", 11 | "build": "yarn clean && tsc -p . && yarn copy-files" 12 | }, 13 | "author": "LeulAria", 14 | "license": "ISC", 15 | "keywords": [ 16 | "react", 17 | "waves", 18 | "waves effect", 19 | "ripple effect", 20 | "react waves", 21 | "react ripple", 22 | "react wave effect", 23 | "react ripple effect", 24 | "react waves effects", 25 | "react ripples effects", 26 | "react waves component", 27 | "react ripples component", 28 | "react waves effect component", 29 | "react ripple effect components" 30 | ], 31 | "peerDependencies": { 32 | "react": ">=16.3.0" 33 | }, 34 | "devDependencies": { 35 | "copyfiles": "^2.4.1", 36 | "rimraf": "^3.0.2", 37 | "typescript": "^4.5.5" 38 | }, 39 | "repository": "LeulAria/react-waves-effect" 40 | } 41 | -------------------------------------------------------------------------------- /main/vite.config.ts: -------------------------------------------------------------------------------- 1 | // import { defineConfig } from 'vite' 2 | // import reactRefresh from '@vitejs/plugin-react-refresh' 3 | 4 | // // https://vitejs.dev/config/ 5 | // export default defineConfig({ 6 | // plugins: [reactRefresh()] 7 | // }) 8 | 9 | import path from "path"; 10 | import {defineConfig} from "vite"; 11 | // import tsconfigPaths from "vite-tsconfig-paths"; 12 | // import eslintPlugin from "vite-plugin-eslint"; 13 | import react from "@vitejs/plugin-react"; 14 | import {viteCommonjs, esbuildCommonjs} from "@originjs/vite-plugin-commonjs"; 15 | 16 | // https://vitejs.dev/config/ 17 | export default defineConfig({ 18 | plugins: [ 19 | // This creates part of the magic. 20 | viteCommonjs(), 21 | 22 | // https://www.npmjs.com/package/@vitejs/plugin-react 23 | react({ 24 | // exclude: /\.stories\.(t|j)sx?$/, 25 | 26 | babel: { 27 | // presets: ['@babel/preset-env'], 28 | // babelrc: true, 29 | parserOpts: { 30 | plugins: [ 31 | "optionalChaining", 32 | "nullishCoalescingOperator", 33 | "logicalAssignment", 34 | ], 35 | }, 36 | }, 37 | }), 38 | ], 39 | 40 | optimizeDeps: { 41 | esbuildOptions: { 42 | plugins: [ 43 | // Solves: 44 | // https://github.com/vitejs/vite/issues/5308 45 | // add the name of your package 46 | esbuildCommonjs(["tiny-slider", "tiny-slider-react"]), 47 | ], 48 | }, 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /main/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Ripple from "./react-waves-effect"; 3 | import "./react-waves-effect/ripple.css"; 4 | 5 | function App() { 6 | return ( 7 |
8 | { 15 | console.log("clicked"); 16 | }} 17 | > 18 |
31 | React Waves Effect 32 |
33 |
34 | 35 |
36 | 37 | { 44 | console.log("clicked"); 45 | }} 46 | > 47 |
60 | React Waves Effect 61 |
62 |
63 |
64 | ); 65 | } 66 | 67 | export default App; 68 | -------------------------------------------------------------------------------- /main/src/react-waves-effect/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface IProps { 4 | pointer?: boolean; 5 | radius?: string; 6 | color?: string; 7 | endWidth?: string; 8 | endHeight?: string; 9 | animationEasing?: string; 10 | animationDuration?: number; 11 | onClick?: () => void; 12 | children: 13 | | string 14 | | JSX.Element 15 | | JSX.Element[] 16 | | React.ReactChild 17 | | React.ReactChildren 18 | | React.ReactChildren[]; 19 | } 20 | 21 | const Ripple = ({ 22 | pointer = true, 23 | radius = '50%', 24 | color = '#FFF', 25 | endWidth = '500px', 26 | endHeight = '500px', 27 | animationEasing = 'linear', 28 | animationDuration = 700, 29 | onClick, 30 | children, 31 | }: IProps) => { 32 | // const charset: string = 33 | // 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 34 | // const id = `${[...Array(5)] 35 | // .map((_) => charset[Math.floor(Math.random() * charset.length)]) 36 | // .join('')}id`; 37 | 38 | const buttonRef = React.useRef(null); 39 | 40 | const addRipple = (e: any) => { 41 | const x = e.clientX - e.target.getBoundingClientRect().left; 42 | const y = e.clientY - e.target.getBoundingClientRect().top; 43 | 44 | const ripples = document.createElement('span'); 45 | ripples.classList.add('wave'); 46 | ripples.style.left = `${x}px`; 47 | ripples.style.top = `${y}px`; 48 | 49 | // add style 50 | ripples.style.width = endWidth; 51 | ripples.style.height = endHeight; 52 | ripples.style.background = color; 53 | ripples.style.borderRadius = radius; 54 | ripples.style.position = `absolute`; 55 | ripples.style.pointerEvents = `none`; 56 | ripples.style.transform = `translate(-50%, -50%)`; 57 | ripples.style.animation = `wave-animate ${animationDuration}ms ${animationEasing} forwards`; 58 | 59 | setTimeout(() => { 60 | ripples.remove(); 61 | }, animationDuration); 62 | 63 | buttonRef.current?.appendChild(ripples); 64 | if (onClick) { 65 | onClick(); 66 | } 67 | }; 68 | 69 | return ( 70 |
82 | {children} 83 |
84 | ); 85 | }; 86 | 87 | export default Ripple; -------------------------------------------------------------------------------- /docs/css/termynal.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap"); 2 | 3 | /** 4 | * termynal.js 5 | * 6 | * @author Ines Montani 7 | * @version 0.0.1 8 | * @license MIT 9 | */ 10 | 11 | :root { 12 | --color-bg: #252a33; 13 | --color-text: #eee; 14 | --color-text-subtle: #a2a2a2; 15 | } 16 | 17 | [data-termynal] { 18 | width: 750px; 19 | max-width: 100%; 20 | background: var(--color-bg); 21 | color: var(--color-text); 22 | font-size: 18px; 23 | font-family: "Space Mono", monospace; 24 | border-radius: 4px; 25 | padding: 75px 45px 35px; 26 | position: relative; 27 | -webkit-box-sizing: border-box; 28 | box-sizing: border-box; 29 | } 30 | 31 | [data-termynal]:before { 32 | content: ""; 33 | position: absolute; 34 | top: 15px; 35 | left: 15px; 36 | display: inline-block; 37 | width: 15px; 38 | height: 15px; 39 | border-radius: 50%; 40 | /* A little hack to display the window buttons in one pseudo element. */ 41 | background: #d9515d; 42 | -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; 43 | box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; 44 | } 45 | 46 | [data-termynal]:after { 47 | content: "bash"; 48 | position: absolute; 49 | color: var(--color-text-subtle); 50 | top: 5px; 51 | left: 0; 52 | width: 100%; 53 | text-align: center; 54 | } 55 | 56 | a[data-terminal-control] { 57 | text-align: right; 58 | display: block; 59 | color: #aebbff; 60 | } 61 | 62 | [data-ty] { 63 | display: block; 64 | line-height: 2; 65 | } 66 | 67 | [data-ty]:before { 68 | /* Set up defaults and ensure empty lines are displayed. */ 69 | content: ""; 70 | display: inline-block; 71 | vertical-align: middle; 72 | } 73 | 74 | [data-ty="input"]:before, 75 | [data-ty-prompt]:before { 76 | margin-right: 0.75em; 77 | color: var(--color-text-subtle); 78 | } 79 | 80 | [data-ty="input"]:before { 81 | content: "$"; 82 | } 83 | 84 | [data-ty][data-ty-prompt]:before { 85 | content: attr(data-ty-prompt); 86 | } 87 | 88 | [data-ty-cursor]:after { 89 | content: attr(data-ty-cursor); 90 | font-family: monospace; 91 | margin-left: 0.5em; 92 | -webkit-animation: blink 1s infinite; 93 | animation: blink 1s infinite; 94 | } 95 | 96 | /* Cursor animation */ 97 | 98 | @-webkit-keyframes blink { 99 | 50% { 100 | opacity: 0; 101 | } 102 | } 103 | 104 | @keyframes blink { 105 | 50% { 106 | opacity: 0; 107 | } 108 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |

React Waves Effect

8 |

9 | package size 10 | npm version 11 | downloads per month 12 |

13 |

14 | 15 | 16 | 17 |

18 |

A Waves effect react component library!

19 | 20 | 26 | 27 | !!! note 28 | React waves effect is a Waves effect react component library to add ripple/wave effects to any components by wrapping the component with Ripple component. 29 | 30 | ## Installation 31 | 32 | yarn 33 | 34 |
35 | ```console 36 | $ yarn add react-waves-effect 37 | 38 | ---> 100% 39 | ``` 40 | 41 |
42 | 43 | npm 44 | 45 |
46 | ```console 47 | $ npm i react-waves-effect 48 | 49 | ---> 100% 50 | ``` 51 | 52 |
53 | 54 | ## Project Scope 55 | 56 | - react-waves-effect v-1.0.0 57 | - [x] Custom Color 58 | - [x] Custom Size 59 | - [x] Custom Animation Speed 60 | - [x] Custom Animation Easing 61 | - [ ] Click/Hover... Event Bubbling... 62 | 63 |
64 | 65 | - react-waves-effect v-2.0.0 66 | - [ ] Button Components Extensions 67 | - [ ] Card Component Extensions 68 | - [ ] Support Other Libraries 69 | - [ ] Other Features 70 | 71 |


72 | 73 | !!! note 74 | You can recommend features by creating an issue in our github repository. 75 | 76 |

77 | 78 | !!! info 79 | 80 | #### other project writtern by the author 81 | 82 | :material-github: 83 | 84 | React Rolebased Router 85 | 86 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: react-waves-effect 2 | site_url: https://leularia.github.io/react-waves-effect/ 3 | site_author: LeulAria 4 | site_description: >- 5 | react-waves-effect is a react hooks for interacting with fireabase eaily. 6 | repo_name: LeulAria/react-waves-effect 7 | repo_url: https://github.com/LeulAria/react-waves-effect 8 | edit_uri: "" 9 | 10 | copyright: Copyright © 2021 react-waves-effect LeulAria 11 | 12 | theme: 13 | name: material 14 | custom_dir: docs/overrides 15 | features: 16 | - navigation.sections 17 | features: 18 | - navigation.tabs 19 | - navigation.tabs.sticky 20 | - search.suggest 21 | icon: 22 | repo: fontawesome/brands/github 23 | admonition: 24 | note: octicons/tag-16 25 | abstract: octicons/checklist-16 26 | info: octicons/info-16 27 | tip: octicons/squirrel-16 28 | success: octicons/check-16 29 | question: octicons/question-16 30 | warning: octicons/alert-16 31 | failure: octicons/x-circle-16 32 | danger: octicons/zap-16 33 | bug: octicons/bug-16 34 | example: octicons/beaker-16 35 | quote: octicons/quote-16 36 | palette: 37 | - scheme: default 38 | primary: purple 39 | accent: yellow 40 | toggle: 41 | icon: material/lightbulb 42 | name: Switch to dark mode 43 | - scheme: slate 44 | primary: purple 45 | accent: indigo 46 | toggle: 47 | icon: material/lightbulb-outline 48 | name: Switch to light mode 49 | 50 | extra_css: 51 | - "css/custom.css" 52 | - "css/termynal.css" 53 | 54 | extra_javascript: 55 | - "https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js" 56 | - "js/termynal.js" 57 | - "js/custom.js" 58 | 59 | plugins: 60 | - search 61 | # - macros 62 | 63 | # Extensions 64 | markdown_extensions: 65 | - admonition 66 | - abbr 67 | - attr_list 68 | - def_list 69 | - footnotes 70 | - meta 71 | - md_in_html 72 | - toc: 73 | permalink: true 74 | - pymdownx.arithmatex: 75 | generic: true 76 | - pymdownx.betterem: 77 | smart_enable: all 78 | - pymdownx.caret 79 | - pymdownx.critic 80 | - pymdownx.details 81 | - pymdownx.emoji: 82 | emoji_index: !!python/name:materialx.emoji.twemoji 83 | emoji_generator: !!python/name:materialx.emoji.to_svg 84 | - pymdownx.highlight 85 | - pymdownx.inlinehilite 86 | - pymdownx.keys 87 | - pymdownx.magiclink: 88 | repo_url_shorthand: true 89 | user: squidfunk 90 | repo: mkdocs-material 91 | - pymdownx.mark 92 | - pymdownx.smartsymbols 93 | - pymdownx.superfences: 94 | custom_fences: 95 | - name: mermaid 96 | class: mermaid 97 | format: !!python/name:pymdownx.superfences.fence_code_format 98 | # - pymdownx.tabbed 99 | - pymdownx.tasklist: 100 | custom_checkbox: true 101 | - pymdownx.tilde 102 | - pymdownx.highlight: 103 | linenums: true 104 | - pymdownx.highlight: 105 | linenums_style: pymdownx.inline 106 | - pymdownx.emoji: 107 | emoji_index: !!python/name:materialx.emoji.twemoji 108 | emoji_generator: !!python/name:materialx.emoji.to_svg 109 | - pymdownx.tasklist: 110 | custom_checkbox: true 111 | clickable_checkbox: true 112 | 113 | nav: 114 | - Home: "index.md" 115 | - Playground: "playground/index.md" 116 | - Doc: "" 117 | - Api: "" 118 | - About: "" 119 | - Release Notes: "" -------------------------------------------------------------------------------- /docs/js/custom.js: -------------------------------------------------------------------------------- 1 | function setupTermynal() { 2 | document.querySelectorAll(".use-termynal").forEach((node) => { 3 | node.style.display = "block"; 4 | new Termynal(node, { 5 | lineDelay: 500, 6 | }); 7 | }); 8 | const progressLiteralStart = "---> 100%"; 9 | const promptLiteralStart = "$ "; 10 | const customPromptLiteralStart = "# "; 11 | const termynalActivateClass = "termy"; 12 | let termynals = []; 13 | 14 | function createTermynals() { 15 | document 16 | .querySelectorAll(`.${termynalActivateClass} .highlight`) 17 | .forEach((node) => { 18 | const text = node.textContent; 19 | const lines = text.split("\n"); 20 | const useLines = []; 21 | let buffer = []; 22 | function saveBuffer() { 23 | if (buffer.length) { 24 | let isBlankSpace = true; 25 | buffer.forEach((line) => { 26 | if (line) { 27 | isBlankSpace = false; 28 | } 29 | }); 30 | dataValue = {}; 31 | if (isBlankSpace) { 32 | dataValue["delay"] = 0; 33 | } 34 | if (buffer[buffer.length - 1] === "") { 35 | // A last single
won't have effect 36 | // so put an additional one 37 | buffer.push(""); 38 | } 39 | const bufferValue = buffer.join("
"); 40 | dataValue["value"] = bufferValue; 41 | useLines.push(dataValue); 42 | buffer = []; 43 | } 44 | } 45 | for (let line of lines) { 46 | if (line === progressLiteralStart) { 47 | saveBuffer(); 48 | useLines.push({ 49 | type: "progress", 50 | }); 51 | } else if (line.startsWith(promptLiteralStart)) { 52 | saveBuffer(); 53 | const value = line.replace(promptLiteralStart, "").trimEnd(); 54 | useLines.push({ 55 | type: "input", 56 | value: value, 57 | }); 58 | } else if (line.startsWith("// ")) { 59 | saveBuffer(); 60 | const value = "💬 " + line.replace("// ", "").trimEnd(); 61 | useLines.push({ 62 | value: value, 63 | class: "termynal-comment", 64 | delay: 0, 65 | }); 66 | } else if (line.startsWith(customPromptLiteralStart)) { 67 | saveBuffer(); 68 | const promptStart = line.indexOf(promptLiteralStart); 69 | if (promptStart === -1) { 70 | console.error("Custom prompt found but no end delimiter", line); 71 | } 72 | const prompt = line 73 | .slice(0, promptStart) 74 | .replace(customPromptLiteralStart, ""); 75 | let value = line.slice(promptStart + promptLiteralStart.length); 76 | useLines.push({ 77 | type: "input", 78 | value: value, 79 | prompt: prompt, 80 | }); 81 | } else { 82 | buffer.push(line); 83 | } 84 | } 85 | saveBuffer(); 86 | const div = document.createElement("div"); 87 | node.replaceWith(div); 88 | const termynal = new Termynal(div, { 89 | lineData: useLines, 90 | noInit: true, 91 | lineDelay: 500, 92 | }); 93 | termynals.push(termynal); 94 | }); 95 | } 96 | 97 | function loadVisibleTermynals() { 98 | termynals = termynals.filter((termynal) => { 99 | if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) { 100 | termynal.init(); 101 | return false; 102 | } 103 | return true; 104 | }); 105 | } 106 | window.addEventListener("scroll", loadVisibleTermynals); 107 | createTermynals(); 108 | loadVisibleTermynals(); 109 | } 110 | 111 | setupTermynal(); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | /* Basic Options */ 5 | // "incremental": true, /* Enable incremental compilation */ 6 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 7 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 8 | // "lib": [], /* Specify library files to be included in the compilation. */ 9 | "lib": [ 10 | "dom", 11 | "esnext" 12 | ], 13 | // "allowJs": true, /* Allow javascript files to be compiled. */ 14 | // "checkJs": true, /* Report errors in .js files. */ 15 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 16 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 17 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 18 | "sourceMap": true, /* Generates corresponding '.map' file. */ 19 | // "outFile": "./", /* Concatenate and emit output to single file. */ 20 | "outDir": "lib", /* Redirect output structure to the directory. */ 21 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 22 | // "composite": true, /* Enable project compilation */ 23 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 24 | // "removeComments": true, /* Do not emit comments to output. */ 25 | // "noEmit": true, /* Do not emit outputs. */ 26 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 27 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 28 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 29 | /* Strict Type-Checking Options */ 30 | "strict": true, /* Enable all strict type-checking options. */ 31 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 32 | // "strictNullChecks": true, /* Enable strict null checks. */ 33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 34 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 35 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | /* Module Resolution Options */ 46 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | /* Advanced Options */ 65 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 66 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 67 | }, 68 | "include": [ 69 | "main/src/react-waves-effect" 70 | ] 71 | } -------------------------------------------------------------------------------- /docs/js/termynal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * termynal.js 3 | * A lightweight, modern and extensible animated terminal window, using 4 | * async/await. 5 | * 6 | * @author Ines Montani 7 | * @version 0.0.1 8 | * @license MIT 9 | */ 10 | 11 | "use strict"; 12 | 13 | /** Generate a terminal widget. */ 14 | class Termynal { 15 | /** 16 | * Construct the widget's settings. 17 | * @param {(string|Node)=} container - Query selector or container element. 18 | * @param {Object=} options - Custom settings. 19 | * @param {string} options.prefix - Prefix to use for data attributes. 20 | * @param {number} options.startDelay - Delay before animation, in ms. 21 | * @param {number} options.typeDelay - Delay between each typed character, in ms. 22 | * @param {number} options.lineDelay - Delay between each line, in ms. 23 | * @param {number} options.progressLength - Number of characters displayed as progress bar. 24 | * @param {string} options.progressChar – Character to use for progress bar, defaults to █. 25 | * @param {number} options.progressPercent - Max percent of progress. 26 | * @param {string} options.cursor – Character to use for cursor, defaults to ▋. 27 | * @param {Object[]} lineData - Dynamically loaded line data objects. 28 | * @param {boolean} options.noInit - Don't initialise the animation. 29 | */ 30 | constructor(container = "#termynal", options = {}) { 31 | this.container = 32 | typeof container === "string" ? document.querySelector(container) : container; 33 | this.pfx = `data-${options.prefix || "ty"}`; 34 | this.originalStartDelay = this.startDelay = 35 | options.startDelay || 36 | parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 37 | 600; 38 | this.originalTypeDelay = this.typeDelay = 39 | options.typeDelay || 40 | parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 41 | 90; 42 | this.originalLineDelay = this.lineDelay = 43 | options.lineDelay || 44 | parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 45 | 1500; 46 | this.progressLength = 47 | options.progressLength || 48 | parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 49 | 40; 50 | this.progressChar = 51 | options.progressChar || 52 | this.container.getAttribute(`${this.pfx}-progressChar`) || 53 | "█"; 54 | this.progressPercent = 55 | options.progressPercent || 56 | parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 57 | 100; 58 | this.cursor = 59 | options.cursor || this.container.getAttribute(`${this.pfx}-cursor`) || "▋"; 60 | this.lineData = this.lineDataToElements(options.lineData || []); 61 | this.loadLines(); 62 | if (!options.noInit) this.init(); 63 | } 64 | 65 | loadLines() { 66 | // Load all the lines and create the container so that the size is fixed 67 | // Otherwise it would be changing and the user viewport would be constantly 68 | // moving as she/he scrolls 69 | const finish = this.generateFinish(); 70 | finish.style.visibility = "hidden"; 71 | this.container.appendChild(finish); 72 | // Appends dynamically loaded lines to existing line elements. 73 | this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat( 74 | this.lineData 75 | ); 76 | for (let line of this.lines) { 77 | line.style.visibility = "hidden"; 78 | this.container.appendChild(line); 79 | } 80 | const restart = this.generateRestart(); 81 | restart.style.visibility = "hidden"; 82 | this.container.appendChild(restart); 83 | this.container.setAttribute("data-termynal", ""); 84 | } 85 | 86 | /** 87 | * Initialise the widget, get lines, clear container and start animation. 88 | */ 89 | init() { 90 | /** 91 | * Calculates width and height of Termynal container. 92 | * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS. 93 | */ 94 | const containerStyle = getComputedStyle(this.container); 95 | this.container.style.width = 96 | containerStyle.width !== "0px" ? containerStyle.width : undefined; 97 | this.container.style.minHeight = 98 | containerStyle.height !== "0px" ? containerStyle.height : undefined; 99 | 100 | this.container.setAttribute("data-termynal", ""); 101 | this.container.innerHTML = ""; 102 | for (let line of this.lines) { 103 | line.style.visibility = "visible"; 104 | } 105 | this.start(); 106 | } 107 | 108 | /** 109 | * Start the animation and rener the lines depending on their data attributes. 110 | */ 111 | async start() { 112 | this.addFinish(); 113 | await this._wait(this.startDelay); 114 | 115 | for (let line of this.lines) { 116 | const type = line.getAttribute(this.pfx); 117 | const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay; 118 | 119 | if (type == "input") { 120 | line.setAttribute(`${this.pfx}-cursor`, this.cursor); 121 | await this.type(line); 122 | await this._wait(delay); 123 | } else if (type == "progress") { 124 | await this.progress(line); 125 | await this._wait(delay); 126 | } else { 127 | this.container.appendChild(line); 128 | await this._wait(delay); 129 | } 130 | 131 | line.removeAttribute(`${this.pfx}-cursor`); 132 | } 133 | this.addRestart(); 134 | this.finishElement.style.visibility = "hidden"; 135 | this.lineDelay = this.originalLineDelay; 136 | this.typeDelay = this.originalTypeDelay; 137 | this.startDelay = this.originalStartDelay; 138 | } 139 | 140 | generateRestart() { 141 | const restart = document.createElement("a"); 142 | restart.onclick = (e) => { 143 | e.preventDefault(); 144 | this.container.innerHTML = ""; 145 | this.init(); 146 | }; 147 | restart.href = "#"; 148 | restart.setAttribute("data-terminal-control", ""); 149 | restart.innerHTML = "restart ↻"; 150 | return restart; 151 | } 152 | 153 | generateFinish() { 154 | const finish = document.createElement("a"); 155 | finish.onclick = (e) => { 156 | e.preventDefault(); 157 | this.lineDelay = 0; 158 | this.typeDelay = 0; 159 | this.startDelay = 0; 160 | }; 161 | finish.href = "#"; 162 | finish.setAttribute("data-terminal-control", ""); 163 | finish.innerHTML = "fast →"; 164 | this.finishElement = finish; 165 | return finish; 166 | } 167 | 168 | addRestart() { 169 | const restart = this.generateRestart(); 170 | this.container.appendChild(restart); 171 | } 172 | 173 | addFinish() { 174 | const finish = this.generateFinish(); 175 | this.container.appendChild(finish); 176 | } 177 | 178 | /** 179 | * Animate a typed line. 180 | * @param {Node} line - The line element to render. 181 | */ 182 | async type(line) { 183 | const chars = [...line.textContent]; 184 | line.textContent = ""; 185 | this.container.appendChild(line); 186 | 187 | for (let char of chars) { 188 | const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; 189 | await this._wait(delay); 190 | line.textContent += char; 191 | } 192 | } 193 | 194 | /** 195 | * Animate a progress bar. 196 | * @param {Node} line - The line element to render. 197 | */ 198 | async progress(line) { 199 | const progressLength = 200 | line.getAttribute(`${this.pfx}-progressLength`) || this.progressLength; 201 | const progressChar = 202 | line.getAttribute(`${this.pfx}-progressChar`) || this.progressChar; 203 | const chars = progressChar.repeat(progressLength); 204 | const progressPercent = 205 | line.getAttribute(`${this.pfx}-progressPercent`) || this.progressPercent; 206 | line.textContent = ""; 207 | this.container.appendChild(line); 208 | 209 | for (let i = 1; i < chars.length + 1; i++) { 210 | await this._wait(this.typeDelay); 211 | const percent = Math.round((i / chars.length) * 100); 212 | line.textContent = `${chars.slice(0, i)} ${percent}%`; 213 | if (percent > progressPercent) { 214 | break; 215 | } 216 | } 217 | } 218 | 219 | /** 220 | * Helper function for animation delays, called with `await`. 221 | * @param {number} time - Timeout, in ms. 222 | */ 223 | _wait(time) { 224 | return new Promise((resolve) => setTimeout(resolve, time)); 225 | } 226 | 227 | /** 228 | * Converts line data objects into line elements. 229 | * 230 | * @param {Object[]} lineData - Dynamically loaded lines. 231 | * @param {Object} line - Line data object. 232 | * @returns {Element[]} - Array of line elements. 233 | */ 234 | lineDataToElements(lineData) { 235 | return lineData.map((line) => { 236 | let div = document.createElement("div"); 237 | div.innerHTML = `${line.value || ""}`; 238 | 239 | return div.firstElementChild; 240 | }); 241 | } 242 | 243 | /** 244 | * Helper function for generating attributes string. 245 | * 246 | * @param {Object} line - Line data object. 247 | * @returns {string} - String of attributes. 248 | */ 249 | _attributes(line) { 250 | let attrs = ""; 251 | for (let prop in line) { 252 | // Custom add class 253 | if (prop === "class") { 254 | attrs += ` class=${line[prop]} `; 255 | continue; 256 | } 257 | if (prop === "type") { 258 | attrs += `${this.pfx}="${line[prop]}" `; 259 | } else if (prop !== "value") { 260 | attrs += `${this.pfx}-${prop}="${line[prop]}" `; 261 | } 262 | } 263 | 264 | return attrs; 265 | } 266 | } 267 | 268 | /** 269 | * HTML API: If current script has container(s) specified, initialise Termynal. 270 | */ 271 | if (document.currentScript.hasAttribute("data-termynal-container")) { 272 | const containers = document.currentScript.getAttribute("data-termynal-container"); 273 | containers.split("|").forEach((container) => new Termynal(container)); 274 | } --------------------------------------------------------------------------------