├── .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 |
9 |
10 |
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 |
10 |
11 |
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 | }
--------------------------------------------------------------------------------