├── src
├── test.ts
├── react-app-env.d.ts
└── index.tsx
├── example
├── README.md
├── src
│ ├── index.js
│ ├── App.css
│ └── App.js
├── public
│ ├── manifest.json
│ └── index.html
└── package.json
├── .travis.yml
├── tsconfig.test.json
├── .gitignore
├── rollup.config.js
├── tsconfig.json
├── README.md
└── package.json
/src/test.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 9
4 | - 8
5 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 |
5 | ReactDOM.render(, document.getElementById("root"));
6 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "use-typewriter",
3 | "name": "use-typewriter",
4 | "start_url": "./index.html",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # builds
8 | build
9 | dist
10 | .rpt2_cache
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 | Typewriter Hook
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-typewriter-example",
3 | "homepage": "https://gielcobben.github.io/use-typewriter",
4 | "version": "0.0.0",
5 | "license": "MIT",
6 | "private": true,
7 | "dependencies": {
8 | "react": "link:../node_modules/react",
9 | "react-dom": "^16.8.0",
10 | "react-scripts": "^2.1.3",
11 | "use-typewriter": "link:.."
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | },
19 | "browserslist": [
20 | ">0.2%",
21 | "not dead",
22 | "not ie <= 11",
23 | "not op_mini all"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "rollup-plugin-typescript2";
2 | import commonjs from "rollup-plugin-commonjs";
3 | import external from "rollup-plugin-peer-deps-external";
4 | import resolve from "rollup-plugin-node-resolve";
5 | import url from "rollup-plugin-url";
6 |
7 | import pkg from "./package.json";
8 |
9 | export default {
10 | input: "src/index.tsx",
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: "cjs",
15 | exports: "named",
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: "es",
21 | exports: "named",
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url({ exclude: ["**/*.svg"] }),
28 | resolve(),
29 | typescript({
30 | rollupCommonJSResolveHack: true,
31 | clean: true
32 | }),
33 | commonjs()
34 | ]
35 | };
36 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build",
4 | "module": "esnext",
5 | "target": "es5",
6 | "lib": [
7 | "es6",
8 | "dom",
9 | "es2016",
10 | "es2017"
11 | ],
12 | "sourceMap": true,
13 | "allowJs": false,
14 | "jsx": "preserve",
15 | "declaration": true,
16 | "moduleResolution": "node",
17 | "forceConsistentCasingInFileNames": true,
18 | "noImplicitReturns": true,
19 | "noImplicitThis": true,
20 | "noImplicitAny": true,
21 | "strictNullChecks": true,
22 | "suppressImplicitAnyIndexErrors": true,
23 | "noUnusedLocals": true,
24 | "noUnusedParameters": true,
25 | "skipLibCheck": true,
26 | "esModuleInterop": true,
27 | "allowSyntheticDefaultImports": true,
28 | "strict": true,
29 | "resolveJsonModule": true,
30 | "isolatedModules": true,
31 | "noEmit": true
32 | },
33 | "include": [
34 | "src"
35 | ],
36 | "exclude": [
37 | "node_modules",
38 | "build",
39 | "dist",
40 | "example",
41 | "rollup.config.js"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hook: Typewriter
2 |
3 | ## Installation
4 |
5 | Install using [Yarn](https://yarnpkg.com):
6 |
7 | ```sh
8 | yarn add use-typewriter
9 | ```
10 |
11 | or NPM:
12 |
13 | ```sh
14 | npm install use-typewriter --save
15 | ```
16 |
17 | ## Usage
18 |
19 | ### Basic
20 |
21 | ```tsx
22 | import React from "react";
23 | import useTypewriter from "use-typewriter";
24 |
25 | const Component = () => {
26 | const currentWord = useTypewriter({
27 | words: ["Hello World"]
28 | });
29 |
30 | return {currentWord}
;
31 | };
32 | ```
33 |
34 | ## API
35 |
36 | ### Options
37 |
38 | | Name | Type | Default | Required | Description |
39 | | ---------- | -------- | ------- | -------- | ---------------------------------------------- |
40 | | words | string[] | [] | Yes | An array of words you want to be typed. |
41 | | min | number | 10 | No | Minimum amount in ms of delay between letters. |
42 | | max | number | 80 | No | Maximum amount in ms of delay between letters. |
43 | | wordDelay | number | 2000 | No | Delay in ms between words in the array. |
44 | | eraseDelay | number | 1000 | No | Delay in ms before earsing the word |
45 |
46 | ## License
47 |
48 | **use-typewriter** is MIT licensed.
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-typewriter",
3 | "version": "0.0.3",
4 | "description": "Typewriter Hook",
5 | "author": "gielcobben ",
6 | "license": "MIT",
7 | "repository": "git@github.com:gielcobben/use-typewriter.git",
8 | "main": "dist/index.js",
9 | "module": "dist/index.es.js",
10 | "jsnext:main": "dist/index.es.js",
11 | "engines": {
12 | "node": ">=8",
13 | "npm": ">=5"
14 | },
15 | "scripts": {
16 | "test": "cross-env CI=1 react-scripts test --env=jsdom",
17 | "test:watch": "react-scripts test --env=jsdom",
18 | "build": "rollup -c",
19 | "start": "rollup -c -w",
20 | "prepare": "yarn run build",
21 | "predeploy": "cd example && yarn install && yarn run build",
22 | "deploy": "gh-pages -d example/build"
23 | },
24 | "dependencies": {},
25 | "peerDependencies": {
26 | "react": "^16.8.0"
27 | },
28 | "devDependencies": {
29 | "@babel/core": "^7.2.2",
30 | "@babel/runtime": "^7.3.1",
31 | "@types/jest": "^23.3.13",
32 | "@types/react": "^16.7.22",
33 | "cross-env": "^5.2.0",
34 | "gh-pages": "^2.0.1",
35 | "react": "^16.8.0",
36 | "react-scripts": "^2.1.3",
37 | "rollup": "^1.1.2",
38 | "rollup-plugin-babel": "^4.3.2",
39 | "rollup-plugin-commonjs": "^9.2.0",
40 | "rollup-plugin-node-resolve": "^4.0.0",
41 | "rollup-plugin-peer-deps-external": "^2.2.0",
42 | "rollup-plugin-typescript2": "^0.19.2",
43 | "rollup-plugin-url": "^2.1.0",
44 | "typescript": "^3.2.4"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | export type typewriterProps = {
4 | words: string[];
5 | min?: number;
6 | max?: number;
7 | wordDelay?: number;
8 | eraseDelay?: number;
9 | };
10 |
11 | const delay = (ms: number) => {
12 | return new Promise(resolve => setTimeout(resolve, ms));
13 | };
14 |
15 | const nextFrame = () => {
16 | return new Promise(resolve => {
17 | requestAnimationFrame(resolve);
18 | });
19 | };
20 |
21 | const randomDelay = async ({ min, max }: { min: number; max: number }) => {
22 | const delay = Math.random() * (max - min) + min;
23 | const startTime = performance.now();
24 |
25 | while (performance.now() - startTime < delay) {
26 | await nextFrame();
27 | }
28 | };
29 |
30 | export default ({
31 | words = [],
32 | min = 10,
33 | max = 80,
34 | wordDelay = 2000,
35 | eraseDelay = 1000
36 | }: typewriterProps) => {
37 | const [currentWord, setCurrentWord] = React.useState(0);
38 | const [word, setWord] = React.useState("");
39 | const string = words[currentWord];
40 |
41 | React.useEffect(() => {
42 | const erase = async () => {
43 | for (let i = 0; i < string.length; i++) {
44 | await randomDelay({ min, max });
45 | setWord((word: string) => word.slice(0, word.length - 1));
46 |
47 | if (i === string.length - 1) {
48 | await delay(eraseDelay);
49 | setCurrentWord(currentWord =>
50 | currentWord === words.length - 1 ? 0 : currentWord + 1
51 | );
52 | }
53 | }
54 | };
55 |
56 | (async () => {
57 | for (let i = 0; i < string.length; i++) {
58 | await randomDelay({ min, max });
59 | setWord((word: string) => word + string.charAt(i));
60 |
61 | if (i === string.length - 1) {
62 | await delay(wordDelay);
63 | erase();
64 | }
65 | }
66 | })();
67 | }, [currentWord]);
68 |
69 | return word;
70 | };
71 |
--------------------------------------------------------------------------------
/example/src/App.css:
--------------------------------------------------------------------------------
1 | ::selection {
2 | color: #FFF;
3 | background: rgba(0, 85, 255, 0.99);
4 | }
5 |
6 | ::-moz-selection {
7 | color: #FFF;
8 | background: rgba(0, 85, 255, 0.99);
9 | }
10 |
11 | html {
12 | box-sizing: border-box;
13 | }
14 |
15 | *,
16 | *::before,
17 | *::after {
18 | box-sizing: inherit;
19 | }
20 |
21 |
22 | html,
23 | body {
24 | width: 100%;
25 | height: 100%;
26 | }
27 |
28 | html {
29 | line-height: 1.15;
30 | -webkit-text-size-adjust: 100%;
31 | }
32 |
33 | body {
34 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
35 | font-size: 18px;
36 | background: #000;
37 | color: #fff;
38 | margin: 0;
39 | }
40 |
41 | body > div {
42 | width: 100%;
43 | height: 100%;
44 | }
45 |
46 | a {
47 | opacity: 0.5;
48 | transition: opacity 0.2s ease;
49 | }
50 |
51 | a:hover {
52 | opacity: 1;
53 | }
54 |
55 | ul {
56 | display: flex;
57 | align-items: center;
58 | list-style: none;
59 | margin: 0 auto;
60 | padding: 0;
61 | }
62 |
63 | li {
64 | margin: 0 20px;
65 | }
66 |
67 | .wrapper {
68 | display: flex;
69 | align-items: center;
70 | justify-content: space-between;
71 | flex-direction: column;
72 | min-height: 100%;
73 | padding: 200px 40px;
74 | }
75 |
76 | .typewriter {
77 | display: flex;
78 | align-items: center;
79 | font-weight: bold;
80 | font-size: 5vw;
81 | height: 5vw;
82 | }
83 |
84 | @keyframes blink {
85 | from, to {
86 | opacity: 1;
87 | }
88 | 50% {
89 | opacity: 0;
90 | }
91 | }
92 |
93 | .cursor {
94 | border-radius: 0.3vw;
95 | width: 0.3vw;
96 | height: 5vw;
97 | background: #05F;
98 | animation: blink 1s linear infinite forwards;
99 | }
100 |
101 | .controls {
102 | max-width: 400px;
103 | display: flex;
104 | flex-direction: column;
105 | margin: 100px auto;
106 | }
107 |
108 | .slider {
109 | margin: 20px;
110 | display: flex;
111 | align-items: center;
112 | justify-content: space-between;
113 | }
114 |
115 | label {
116 | width: 150px;
117 | }
118 |
119 | input[type="range"] {
120 | appearance: none;
121 | height: 2px;
122 | border-radius: 2px;
123 | outline: none;
124 | width: 250px;
125 | }
126 |
127 | input[type="range"]::-webkit-slider-thumb {
128 | appearance: none;
129 | width: 30px;
130 | height: 30px;
131 | border-radius: 20px;
132 | background: #fff;
133 | }
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import useTypewriter from "use-typewriter";
3 | import "./App.css";
4 |
5 | const App = () => {
6 | const [min, setMin] = useState(10);
7 | const [max, setMax] = useState(80);
8 | const [wordDelay, setWordDelay] = useState(2000);
9 | const [eraseDelay, setEraseDelay] = useState(1000);
10 |
11 | const currentWord = useTypewriter({
12 | words: ["Hello", "World", "This is", "a hook"],
13 | min,
14 | max,
15 | wordDelay,
16 | eraseDelay
17 | });
18 |
19 | return (
20 |
21 |
22 | {currentWord}
23 |
24 |
86 |
132 |
133 | );
134 | };
135 |
136 | export default App;
137 |
--------------------------------------------------------------------------------