├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── .prettierrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── next.config.js
├── package-lock.json
├── package.json
├── pages
│ ├── component.jsx
│ ├── hook.jsx
│ └── index.jsx
└── react-konami-code-2.3.0.tgz
├── package-lock.json
├── package.json
├── src
├── Konami.tsx
├── index.ts
├── useKonami.ts
└── utils
│ ├── Timer.tsx
│ ├── array.ts
│ └── consts.ts
├── test
├── Konami.spec.tsx
├── __snapshots__
│ └── Konami.spec.tsx.snap
└── jestSetup.ts
├── tsconfig.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/react"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "jest": true
5 | },
6 | "globals": {
7 | "shallow": true,
8 | "mount": true
9 | },
10 | "parser": "@typescript-eslint/parser",
11 | "plugins": [
12 | "@typescript-eslint"
13 | ],
14 | "settings": {
15 | "import/extensions": [".js", ".jsx", ".ts", ".tsx"]
16 | },
17 | "extends": [
18 | "airbnb",
19 | "plugin:@typescript-eslint/eslint-recommended",
20 | "plugin:@typescript-eslint/recommended",
21 | "plugin:import/errors",
22 | "plugin:import/warnings",
23 | "plugin:import/typescript"
24 | ],
25 |
26 | "rules": {
27 | "jsx-a11y/anchor-is-valid": "off",
28 | "no-underscore-dangle": ["error", { "allowAfterThis": true }],
29 | "import/prefer-default-export": "off",
30 | "lines-between-class-members": "off",
31 | "react/prop-types": [0],
32 | "react/destructuring-assignment": "off",
33 | "react/static-property-placement": "off",
34 | "react/jsx-filename-extension": [1, { "extensions": [".tsx", ".ts"] }],
35 | "import/extensions": [
36 | "error",
37 | "ignorePackages",
38 | {
39 | "js": "never",
40 | "jsx": "never",
41 | "ts": "never",
42 | "tsx": "never"
43 | }
44 | ]
45 | }
46 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | dist/
3 | node_modules/
4 | .next
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | coverage
2 | src
3 | test
4 | example
5 |
6 | .prettierrc
7 | .babelrc
8 | .eslintrc
9 | .gitignore
10 | .travis.yml
11 | webpack.config.js
12 | tsconfig.json
13 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "singleQuote": true,
5 | "trailingComma": "all"
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "12"
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # React Konami Code Changelog
2 |
3 | ### 2.3.0
4 | Package updates and fixes
5 | - Updated multiple dependencies
6 | - Fix TS error for children props
7 | - Lint updates
8 | ### 2.2.2
9 | Fix type export definition.
10 | ### 2.2.1
11 | Fix bug with disabled condition change not triggering a render
12 |
13 | ### 2.2.0
14 | - Migrated library to TS: full TS support.
15 | - Added optimization using `shouldComponentUpdate`. Avoids rerenders for every timer interval and input.
16 |
17 |
18 | ### 2.1.1
19 | Updated package description and improved documentation.
20 |
21 | ### 2.1.0
22 | Readded `useKonami` hook.
23 |
24 | ### 2.0.0-beta.1
25 | Removed usage of `useKonami` custom hook until it's ready to be used.
26 |
27 | ### 2.0.0-beta.0
28 | Add `useKonami` custom hook.
29 |
30 | ### v1.4.3
31 | Add missing `onTimeout` to type definitions.
32 |
33 | ### v1.4.2
34 | Unlisten `keyup` event on `componentWillUnmount`.
35 |
36 | ### v1.4.0
37 | Fixes the following warning by properly clearing the timeout on `componentWillUnmount`:
38 |
39 | ```
40 | Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
41 | ```
42 |
43 | ### v1.3.0
44 | Adds typescript support.
45 |
46 | ### v1.2.3
47 | Fixes third party vulnerabilities. v1.2.2 introduced an error with the dist file.
48 |
49 | ~~v1.2.2
50 | Fixes third party vulnerabilities.~~
51 |
52 | ### v1.2.0
53 | Adds testing and coverage support.
54 |
55 | ### v1.1.3
56 | Updated package.json keywords for better searching.
57 |
58 | ### v1.1.2
59 | Updated README with badges.
60 |
61 | ### v1.1.1
62 | Initial release.
63 |
64 | Since this is my first npm package I had some issues with versioning, but updates will be pushed from this release as usual.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Vinicius Araujo
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-konami-code [](https://gitHub.com/vmarchesin/react-konami-code/)
2 |
3 | Trigger an easter egg by pressing a sequence of keys. Also available as a custom hook.
4 |
5 | []()
6 | []()
7 | []()
8 | 
9 | 
10 | [](https://snyk.io/test/github/vmarchesin/react-konami-code)
11 |
12 | ## Install
13 |
14 | [](https://www.npmjs.com/package/react-konami-code)
15 |
16 | ```shell
17 | npm i react-konami-code -S
18 | ```
19 |
20 | ## Example
21 | Clone this repo and run `npm start` in the `example` folder.
22 |
23 | ## Usage
24 | ### CommonJS Module (Webpack or Browserify)
25 |
26 | ```jsx
27 | import React from 'react';
28 | import Konami from 'react-konami-code';
29 |
30 | export default class App extends React.Component {
31 | easterEgg = () => {
32 | alert('Hey, you typed the Konami Code!');
33 | }
34 |
35 | render = () => (
36 |
37 | {"Hey, I'm an Easter Egg! Look at me!"}
38 |
39 | )
40 | }
41 | ```
42 |
43 | ### TypeScript
44 |
45 | ```tsx
46 | import * as React from 'react';
47 | import Konami from 'react-konami-code';
48 |
49 | export default class App extends React.Component {
50 | public render = () => (
51 |
52 | {"Hey, I'm an Easter Egg! Look at me!"}
53 |
54 | )
55 |
56 | private easterEgg = () => {
57 | alert('Hey, you typed the Konami Code!');
58 | }
59 | }
60 | ```
61 |
62 | ### Custom Hook
63 |
64 | Refer to the [Using the custom Hook](#hooks) section.
65 |
66 | ## Component
67 |
68 | ### Children
69 |
70 | The content to be displayed should be passed as `children` inside the `Konami` component, and it will be wrapped inside a div. You can however not pass any children, and then just use the [`action`](#action) callback to fire your easter egg.
71 |
72 | You can pass `children` and [`action`](#action) at the same time to display some content and fire a secondary action.
73 |
74 | ## Props
75 | * [`action`](#action)
76 | * [`className`](#className)
77 | * [`code`](#code)
78 | * [`disabled`](#disabled)
79 | * [`onTimeout`](#onTimeout)
80 | * [`resetDelay`](#resetDelay)
81 | * [`timeout`](#timeout)
82 |
83 |
84 | #### action `function`
85 | *Default:* `null`
86 |
87 | The callback action that should fire when the [`code`](#code) is input.
88 |
89 |
90 | #### className `string`
91 | *Default:* `""`
92 |
93 | CSS classes can be applied to the div wrapping your secret content. By default the div will always have the `konami` className.
94 |
95 | ```jsx
96 |
97 | {"Hey, I'm an Easter Egg!"}
98 |
99 | ```
100 | will result in:
101 | ```html
102 |
103 | Hey, I'm an Easter Egg!
104 |
105 | ```
106 |
107 |
108 | #### code `Array`
109 | *Default:* `[38,38,40,40,37,39,37,39,66,65]`
110 |
111 | An array with the sequence of keyCodes necessary to trigger the [`action`](#action). The default code is the Konami Code: `↑ ↑ ↓ ↓ ← → ← → B A`
112 |
113 | You can find the keyCodes for each character [here](https://www.w3.org/2002/09/tests/keys.html).
114 |
115 |
116 | #### disabled `boolean`
117 | *Default:* `false`
118 |
119 | If the trigger should be disabled or not. This is dynamic and you can enable/disable at will. The [`action`](#action) callback will only trigger when `disabled == false`.
120 |
121 |
122 | #### onTimeout `function`
123 |
124 | The callback to fire when the [`timeout`](#timeout) is finished, if any.
125 |
126 |
127 | #### resetDelay `number`
128 | *Default:* `1000`
129 |
130 | The delay interval on which you need to start the input again. If you set it to `0` it will never reset the user input. Value should be in ms.
131 |
132 |
133 | #### timeout `number`
134 | *Default:* `null`
135 |
136 | The timeout to hide the easter egg. When the timeout is finished it will set `display: none` to the wrapping div and will fire [`onTimeout`](#onTimeout). By default it runs forever. Value should be in ms.
137 |
138 |
139 | ## Using the custom Hook
140 |
141 | If you want to call an action without rendering children or handling timeouts it's recommended to use the `useKonami` hook.
142 |
143 | ```jsx
144 | import React from 'react';
145 | import { useKonami } from 'react-konami-code';
146 | const easterEgg = () => {
147 | alert('Hey, you typed the Konami Code!');
148 | }
149 | export default () => {
150 | useKonami(easterEgg);
151 | return ;
152 | };
153 | ```
154 |
155 | ### API
156 | `useKonami(action, [options])`
157 |
158 |
159 | #### action `function`
160 | *Required*
161 |
162 | The callback action that should fire when the [`code`](#hooks-options) is input.
163 |
164 |
165 | #### options `object`
166 | - code
167 |
168 | *Default:* `[38,38,40,40,37,39,37,39,66,65]`
169 |
170 | An array with the sequence of keyCodes necessary to trigger the [`action`](#hooks-action). Refer to main [`code`](#code) section for the keyCodes.
171 |
172 | ## License
173 |
174 | [MIT](https://github.com/vmarchesin/react-konami-code/blob/master/LICENSE)
175 |
176 | ## Contact
177 |
178 | You can reach me on my [Github](https://github.com/vmarchesin) or send an email to [dev@vmarches.in](mailto:dev@vmarches.in).
179 |
--------------------------------------------------------------------------------
/example/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | eslint: {
3 | // Warning: This allows production builds to successfully complete even if
4 | // your project has ESLint errors.
5 | ignoreDuringBuilds: true,
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/example/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-konami-code-example",
3 | "version": "1.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "react-konami-code-example",
9 | "version": "1.0.0",
10 | "hasInstallScript": true,
11 | "dependencies": {
12 | "next": "12.1.6",
13 | "react": "18.1.0",
14 | "react-dom": "18.1.0",
15 | "react-konami-code": "file:react-konami-code-2.3.0.tgz"
16 | }
17 | },
18 | "node_modules/react-konami-code": {
19 | "version": "2.3.0",
20 | "resolved": "file:react-konami-code-2.3.0.tgz",
21 | "integrity": "sha512-A7B8on7qqJ9RwVFqKU6DPtqVOvXJyOz2L1adwlNXD+nVWvFeMS7ru3BioaZWNYI5Ni1/LJnIQAWpgJJRFxqmIg==",
22 | "license": "MIT",
23 | "dependencies": {
24 | "prop-types": "^15.8.1"
25 | },
26 | "peerDependencies": {
27 | "react": ">= 16.8.0",
28 | "react-dom": ">= 16.8.0"
29 | }
30 | },
31 | "node_modules/caniuse-lite": {
32 | "version": "1.0.30001338",
33 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001338.tgz",
34 | "integrity": "sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ==",
35 | "funding": [
36 | {
37 | "type": "opencollective",
38 | "url": "https://opencollective.com/browserslist"
39 | },
40 | {
41 | "type": "tidelift",
42 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
43 | }
44 | ]
45 | },
46 | "node_modules/@next/env": {
47 | "version": "12.1.6",
48 | "resolved": "https://registry.npmjs.org/@next/env/-/env-12.1.6.tgz",
49 | "integrity": "sha512-Te/OBDXFSodPU6jlXYPAXpmZr/AkG6DCATAxttQxqOWaq6eDFX25Db3dK0120GZrSZmv4QCe9KsZmJKDbWs4OA=="
50 | },
51 | "node_modules/nanoid": {
52 | "version": "3.3.4",
53 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
54 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
55 | "bin": {
56 | "nanoid": "bin/nanoid.cjs"
57 | },
58 | "engines": {
59 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
60 | }
61 | },
62 | "node_modules/@next/swc-linux-x64-gnu": {
63 | "version": "12.1.6",
64 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.6.tgz",
65 | "integrity": "sha512-AC7jE4Fxpn0s3ujngClIDTiEM/CQiB2N2vkcyWWn6734AmGT03Duq6RYtPMymFobDdAtZGFZd5nR95WjPzbZAQ==",
66 | "cpu": [
67 | "x64"
68 | ],
69 | "optional": true,
70 | "os": [
71 | "linux"
72 | ],
73 | "engines": {
74 | "node": ">= 10"
75 | }
76 | },
77 | "node_modules/@next/swc-win32-ia32-msvc": {
78 | "version": "12.1.6",
79 | "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.6.tgz",
80 | "integrity": "sha512-8ZWoj6nCq6fI1yCzKq6oK0jE6Mxlz4MrEsRyu0TwDztWQWe7rh4XXGLAa2YVPatYcHhMcUL+fQQbqd1MsgaSDA==",
81 | "cpu": [
82 | "ia32"
83 | ],
84 | "optional": true,
85 | "os": [
86 | "win32"
87 | ],
88 | "engines": {
89 | "node": ">= 10"
90 | }
91 | },
92 | "node_modules/@next/swc-android-arm64": {
93 | "version": "12.1.6",
94 | "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.1.6.tgz",
95 | "integrity": "sha512-EboEk3ROYY7U6WA2RrMt/cXXMokUTXXfnxe2+CU+DOahvbrO8QSWhlBl9I9ZbFzJx28AGB9Yo3oQHCvph/4Lew==",
96 | "cpu": [
97 | "arm64"
98 | ],
99 | "optional": true,
100 | "os": [
101 | "android"
102 | ],
103 | "engines": {
104 | "node": ">= 10"
105 | }
106 | },
107 | "node_modules/react-dom": {
108 | "version": "18.1.0",
109 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
110 | "integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
111 | "dependencies": {
112 | "loose-envify": "^1.1.0",
113 | "scheduler": "^0.22.0"
114 | },
115 | "peerDependencies": {
116 | "react": "^18.1.0"
117 | }
118 | },
119 | "node_modules/react": {
120 | "version": "18.1.0",
121 | "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
122 | "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
123 | "dependencies": {
124 | "loose-envify": "^1.1.0"
125 | },
126 | "engines": {
127 | "node": ">=0.10.0"
128 | }
129 | },
130 | "node_modules/@next/swc-android-arm-eabi": {
131 | "version": "12.1.6",
132 | "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.1.6.tgz",
133 | "integrity": "sha512-BxBr3QAAAXWgk/K7EedvzxJr2dE014mghBSA9iOEAv0bMgF+MRq4PoASjuHi15M2zfowpcRG8XQhMFtxftCleQ==",
134 | "cpu": [
135 | "arm"
136 | ],
137 | "optional": true,
138 | "os": [
139 | "android"
140 | ],
141 | "engines": {
142 | "node": ">= 10"
143 | }
144 | },
145 | "node_modules/styled-jsx": {
146 | "version": "5.0.2",
147 | "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.2.tgz",
148 | "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==",
149 | "engines": {
150 | "node": ">= 12.0.0"
151 | },
152 | "peerDependencies": {
153 | "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
154 | },
155 | "peerDependenciesMeta": {
156 | "@babel/core": {
157 | "optional": true
158 | },
159 | "babel-plugin-macros": {
160 | "optional": true
161 | }
162 | }
163 | },
164 | "node_modules/@next/swc-win32-arm64-msvc": {
165 | "version": "12.1.6",
166 | "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.6.tgz",
167 | "integrity": "sha512-3UTOL/5XZSKFelM7qN0it35o3Cegm6LsyuERR3/OoqEExyj3aCk7F025b54/707HTMAnjlvQK3DzLhPu/xxO4g==",
168 | "cpu": [
169 | "arm64"
170 | ],
171 | "optional": true,
172 | "os": [
173 | "win32"
174 | ],
175 | "engines": {
176 | "node": ">= 10"
177 | }
178 | },
179 | "node_modules/@next/swc-darwin-arm64": {
180 | "version": "12.1.6",
181 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.6.tgz",
182 | "integrity": "sha512-P0EXU12BMSdNj1F7vdkP/VrYDuCNwBExtRPDYawgSUakzi6qP0iKJpya2BuLvNzXx+XPU49GFuDC5X+SvY0mOw==",
183 | "cpu": [
184 | "arm64"
185 | ],
186 | "optional": true,
187 | "os": [
188 | "darwin"
189 | ],
190 | "engines": {
191 | "node": ">= 10"
192 | }
193 | },
194 | "node_modules/js-tokens": {
195 | "version": "4.0.0",
196 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
197 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
198 | },
199 | "node_modules/react-is": {
200 | "version": "16.13.1",
201 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
202 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
203 | },
204 | "node_modules/@next/swc-linux-arm64-gnu": {
205 | "version": "12.1.6",
206 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.6.tgz",
207 | "integrity": "sha512-53QOvX1jBbC2ctnmWHyRhMajGq7QZfl974WYlwclXarVV418X7ed7o/EzGY+YVAEKzIVaAB9JFFWGXn8WWo0gQ==",
208 | "cpu": [
209 | "arm64"
210 | ],
211 | "optional": true,
212 | "os": [
213 | "linux"
214 | ],
215 | "engines": {
216 | "node": ">= 10"
217 | }
218 | },
219 | "node_modules/object-assign": {
220 | "version": "4.1.1",
221 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
222 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
223 | "engines": {
224 | "node": ">=0.10.0"
225 | }
226 | },
227 | "node_modules/loose-envify": {
228 | "version": "1.4.0",
229 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
230 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
231 | "dependencies": {
232 | "js-tokens": "^3.0.0 || ^4.0.0"
233 | },
234 | "bin": {
235 | "loose-envify": "cli.js"
236 | }
237 | },
238 | "node_modules/@next/swc-darwin-x64": {
239 | "version": "12.1.6",
240 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.6.tgz",
241 | "integrity": "sha512-9FptMnbgHJK3dRDzfTpexs9S2hGpzOQxSQbe8omz6Pcl7rnEp9x4uSEKY51ho85JCjL4d0tDLBcXEJZKKLzxNg==",
242 | "cpu": [
243 | "x64"
244 | ],
245 | "optional": true,
246 | "os": [
247 | "darwin"
248 | ],
249 | "engines": {
250 | "node": ">= 10"
251 | }
252 | },
253 | "node_modules/postcss": {
254 | "version": "8.4.5",
255 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
256 | "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
257 | "dependencies": {
258 | "nanoid": "^3.1.30",
259 | "picocolors": "^1.0.0",
260 | "source-map-js": "^1.0.1"
261 | },
262 | "engines": {
263 | "node": "^10 || ^12 || >=14"
264 | },
265 | "funding": {
266 | "type": "opencollective",
267 | "url": "https://opencollective.com/postcss/"
268 | }
269 | },
270 | "node_modules/source-map-js": {
271 | "version": "1.0.2",
272 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
273 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
274 | "engines": {
275 | "node": ">=0.10.0"
276 | }
277 | },
278 | "node_modules/next": {
279 | "version": "12.1.6",
280 | "resolved": "https://registry.npmjs.org/next/-/next-12.1.6.tgz",
281 | "integrity": "sha512-cebwKxL3/DhNKfg9tPZDQmbRKjueqykHHbgaoG4VBRH3AHQJ2HO0dbKFiS1hPhe1/qgc2d/hFeadsbPicmLD+A==",
282 | "dependencies": {
283 | "@next/env": "12.1.6",
284 | "caniuse-lite": "^1.0.30001332",
285 | "postcss": "8.4.5",
286 | "styled-jsx": "5.0.2"
287 | },
288 | "bin": {
289 | "next": "dist/bin/next"
290 | },
291 | "engines": {
292 | "node": ">=12.22.0"
293 | },
294 | "optionalDependencies": {
295 | "@next/swc-android-arm-eabi": "12.1.6",
296 | "@next/swc-android-arm64": "12.1.6",
297 | "@next/swc-darwin-arm64": "12.1.6",
298 | "@next/swc-darwin-x64": "12.1.6",
299 | "@next/swc-linux-arm-gnueabihf": "12.1.6",
300 | "@next/swc-linux-arm64-gnu": "12.1.6",
301 | "@next/swc-linux-arm64-musl": "12.1.6",
302 | "@next/swc-linux-x64-gnu": "12.1.6",
303 | "@next/swc-linux-x64-musl": "12.1.6",
304 | "@next/swc-win32-arm64-msvc": "12.1.6",
305 | "@next/swc-win32-ia32-msvc": "12.1.6",
306 | "@next/swc-win32-x64-msvc": "12.1.6"
307 | },
308 | "peerDependencies": {
309 | "fibers": ">= 3.1.0",
310 | "node-sass": "^6.0.0 || ^7.0.0",
311 | "react": "^17.0.2 || ^18.0.0-0",
312 | "react-dom": "^17.0.2 || ^18.0.0-0",
313 | "sass": "^1.3.0"
314 | },
315 | "peerDependenciesMeta": {
316 | "fibers": {
317 | "optional": true
318 | },
319 | "node-sass": {
320 | "optional": true
321 | },
322 | "sass": {
323 | "optional": true
324 | }
325 | }
326 | },
327 | "node_modules/@next/swc-linux-arm64-musl": {
328 | "version": "12.1.6",
329 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.6.tgz",
330 | "integrity": "sha512-CMWAkYqfGdQCS+uuMA1A2UhOfcUYeoqnTW7msLr2RyYAys15pD960hlDfq7QAi8BCAKk0sQ2rjsl0iqMyziohQ==",
331 | "cpu": [
332 | "arm64"
333 | ],
334 | "optional": true,
335 | "os": [
336 | "linux"
337 | ],
338 | "engines": {
339 | "node": ">= 10"
340 | }
341 | },
342 | "node_modules/@next/swc-win32-x64-msvc": {
343 | "version": "12.1.6",
344 | "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.6.tgz",
345 | "integrity": "sha512-4ZEwiRuZEicXhXqmhw3+de8Z4EpOLQj/gp+D9fFWo6ii6W1kBkNNvvEx4A90ugppu+74pT1lIJnOuz3A9oQeJA==",
346 | "cpu": [
347 | "x64"
348 | ],
349 | "optional": true,
350 | "os": [
351 | "win32"
352 | ],
353 | "engines": {
354 | "node": ">= 10"
355 | }
356 | },
357 | "node_modules/prop-types": {
358 | "version": "15.8.1",
359 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
360 | "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
361 | "dependencies": {
362 | "loose-envify": "^1.4.0",
363 | "object-assign": "^4.1.1",
364 | "react-is": "^16.13.1"
365 | }
366 | },
367 | "node_modules/picocolors": {
368 | "version": "1.0.0",
369 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
370 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
371 | },
372 | "node_modules/scheduler": {
373 | "version": "0.22.0",
374 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
375 | "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
376 | "dependencies": {
377 | "loose-envify": "^1.1.0"
378 | }
379 | },
380 | "node_modules/@next/swc-linux-x64-musl": {
381 | "version": "12.1.6",
382 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.6.tgz",
383 | "integrity": "sha512-c9Vjmi0EVk0Kou2qbrynskVarnFwfYIi+wKufR9Ad7/IKKuP6aEhOdZiIIdKsYWRtK2IWRF3h3YmdnEa2WLUag==",
384 | "cpu": [
385 | "x64"
386 | ],
387 | "optional": true,
388 | "os": [
389 | "linux"
390 | ],
391 | "engines": {
392 | "node": ">= 10"
393 | }
394 | },
395 | "node_modules/@next/swc-linux-arm-gnueabihf": {
396 | "version": "12.1.6",
397 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.6.tgz",
398 | "integrity": "sha512-PvfEa1RR55dsik/IDkCKSFkk6ODNGJqPY3ysVUZqmnWMDSuqFtf7BPWHFa/53znpvVB5XaJ5Z1/6aR5CTIqxPw==",
399 | "cpu": [
400 | "arm"
401 | ],
402 | "optional": true,
403 | "os": [
404 | "linux"
405 | ],
406 | "engines": {
407 | "node": ">= 10"
408 | }
409 | }
410 | },
411 | "dependencies": {
412 | "@next/env": {
413 | "version": "12.1.6",
414 | "resolved": "https://registry.npmjs.org/@next/env/-/env-12.1.6.tgz",
415 | "integrity": "sha512-Te/OBDXFSodPU6jlXYPAXpmZr/AkG6DCATAxttQxqOWaq6eDFX25Db3dK0120GZrSZmv4QCe9KsZmJKDbWs4OA=="
416 | },
417 | "@next/swc-android-arm-eabi": {
418 | "version": "12.1.6",
419 | "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.1.6.tgz",
420 | "integrity": "sha512-BxBr3QAAAXWgk/K7EedvzxJr2dE014mghBSA9iOEAv0bMgF+MRq4PoASjuHi15M2zfowpcRG8XQhMFtxftCleQ==",
421 | "optional": true
422 | },
423 | "@next/swc-android-arm64": {
424 | "version": "12.1.6",
425 | "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.1.6.tgz",
426 | "integrity": "sha512-EboEk3ROYY7U6WA2RrMt/cXXMokUTXXfnxe2+CU+DOahvbrO8QSWhlBl9I9ZbFzJx28AGB9Yo3oQHCvph/4Lew==",
427 | "optional": true
428 | },
429 | "@next/swc-darwin-arm64": {
430 | "version": "12.1.6",
431 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.6.tgz",
432 | "integrity": "sha512-P0EXU12BMSdNj1F7vdkP/VrYDuCNwBExtRPDYawgSUakzi6qP0iKJpya2BuLvNzXx+XPU49GFuDC5X+SvY0mOw==",
433 | "optional": true
434 | },
435 | "@next/swc-darwin-x64": {
436 | "version": "12.1.6",
437 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.6.tgz",
438 | "integrity": "sha512-9FptMnbgHJK3dRDzfTpexs9S2hGpzOQxSQbe8omz6Pcl7rnEp9x4uSEKY51ho85JCjL4d0tDLBcXEJZKKLzxNg==",
439 | "optional": true
440 | },
441 | "@next/swc-linux-arm-gnueabihf": {
442 | "version": "12.1.6",
443 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.6.tgz",
444 | "integrity": "sha512-PvfEa1RR55dsik/IDkCKSFkk6ODNGJqPY3ysVUZqmnWMDSuqFtf7BPWHFa/53znpvVB5XaJ5Z1/6aR5CTIqxPw==",
445 | "optional": true
446 | },
447 | "@next/swc-linux-arm64-gnu": {
448 | "version": "12.1.6",
449 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.6.tgz",
450 | "integrity": "sha512-53QOvX1jBbC2ctnmWHyRhMajGq7QZfl974WYlwclXarVV418X7ed7o/EzGY+YVAEKzIVaAB9JFFWGXn8WWo0gQ==",
451 | "optional": true
452 | },
453 | "@next/swc-linux-arm64-musl": {
454 | "version": "12.1.6",
455 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.6.tgz",
456 | "integrity": "sha512-CMWAkYqfGdQCS+uuMA1A2UhOfcUYeoqnTW7msLr2RyYAys15pD960hlDfq7QAi8BCAKk0sQ2rjsl0iqMyziohQ==",
457 | "optional": true
458 | },
459 | "@next/swc-linux-x64-gnu": {
460 | "version": "12.1.6",
461 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.6.tgz",
462 | "integrity": "sha512-AC7jE4Fxpn0s3ujngClIDTiEM/CQiB2N2vkcyWWn6734AmGT03Duq6RYtPMymFobDdAtZGFZd5nR95WjPzbZAQ==",
463 | "optional": true
464 | },
465 | "@next/swc-linux-x64-musl": {
466 | "version": "12.1.6",
467 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.6.tgz",
468 | "integrity": "sha512-c9Vjmi0EVk0Kou2qbrynskVarnFwfYIi+wKufR9Ad7/IKKuP6aEhOdZiIIdKsYWRtK2IWRF3h3YmdnEa2WLUag==",
469 | "optional": true
470 | },
471 | "@next/swc-win32-arm64-msvc": {
472 | "version": "12.1.6",
473 | "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.6.tgz",
474 | "integrity": "sha512-3UTOL/5XZSKFelM7qN0it35o3Cegm6LsyuERR3/OoqEExyj3aCk7F025b54/707HTMAnjlvQK3DzLhPu/xxO4g==",
475 | "optional": true
476 | },
477 | "@next/swc-win32-ia32-msvc": {
478 | "version": "12.1.6",
479 | "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.6.tgz",
480 | "integrity": "sha512-8ZWoj6nCq6fI1yCzKq6oK0jE6Mxlz4MrEsRyu0TwDztWQWe7rh4XXGLAa2YVPatYcHhMcUL+fQQbqd1MsgaSDA==",
481 | "optional": true
482 | },
483 | "@next/swc-win32-x64-msvc": {
484 | "version": "12.1.6",
485 | "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.6.tgz",
486 | "integrity": "sha512-4ZEwiRuZEicXhXqmhw3+de8Z4EpOLQj/gp+D9fFWo6ii6W1kBkNNvvEx4A90ugppu+74pT1lIJnOuz3A9oQeJA==",
487 | "optional": true
488 | },
489 | "caniuse-lite": {
490 | "version": "1.0.30001338",
491 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001338.tgz",
492 | "integrity": "sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ=="
493 | },
494 | "js-tokens": {
495 | "version": "4.0.0",
496 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
497 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
498 | },
499 | "loose-envify": {
500 | "version": "1.4.0",
501 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
502 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
503 | "requires": {
504 | "js-tokens": "^3.0.0 || ^4.0.0"
505 | }
506 | },
507 | "nanoid": {
508 | "version": "3.3.4",
509 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
510 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
511 | },
512 | "next": {
513 | "version": "12.1.6",
514 | "resolved": "https://registry.npmjs.org/next/-/next-12.1.6.tgz",
515 | "integrity": "sha512-cebwKxL3/DhNKfg9tPZDQmbRKjueqykHHbgaoG4VBRH3AHQJ2HO0dbKFiS1hPhe1/qgc2d/hFeadsbPicmLD+A==",
516 | "requires": {
517 | "@next/env": "12.1.6",
518 | "@next/swc-android-arm-eabi": "12.1.6",
519 | "@next/swc-android-arm64": "12.1.6",
520 | "@next/swc-darwin-arm64": "12.1.6",
521 | "@next/swc-darwin-x64": "12.1.6",
522 | "@next/swc-linux-arm-gnueabihf": "12.1.6",
523 | "@next/swc-linux-arm64-gnu": "12.1.6",
524 | "@next/swc-linux-arm64-musl": "12.1.6",
525 | "@next/swc-linux-x64-gnu": "12.1.6",
526 | "@next/swc-linux-x64-musl": "12.1.6",
527 | "@next/swc-win32-arm64-msvc": "12.1.6",
528 | "@next/swc-win32-ia32-msvc": "12.1.6",
529 | "@next/swc-win32-x64-msvc": "12.1.6",
530 | "caniuse-lite": "^1.0.30001332",
531 | "postcss": "8.4.5",
532 | "styled-jsx": "5.0.2"
533 | }
534 | },
535 | "object-assign": {
536 | "version": "4.1.1",
537 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
538 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
539 | },
540 | "picocolors": {
541 | "version": "1.0.0",
542 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
543 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
544 | },
545 | "postcss": {
546 | "version": "8.4.5",
547 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
548 | "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
549 | "requires": {
550 | "nanoid": "^3.1.30",
551 | "picocolors": "^1.0.0",
552 | "source-map-js": "^1.0.1"
553 | }
554 | },
555 | "prop-types": {
556 | "version": "15.8.1",
557 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
558 | "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
559 | "requires": {
560 | "loose-envify": "^1.4.0",
561 | "object-assign": "^4.1.1",
562 | "react-is": "^16.13.1"
563 | }
564 | },
565 | "react": {
566 | "version": "18.1.0",
567 | "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
568 | "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
569 | "requires": {
570 | "loose-envify": "^1.1.0"
571 | }
572 | },
573 | "react-dom": {
574 | "version": "18.1.0",
575 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
576 | "integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
577 | "requires": {
578 | "loose-envify": "^1.1.0",
579 | "scheduler": "^0.22.0"
580 | }
581 | },
582 | "react-is": {
583 | "version": "16.13.1",
584 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
585 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
586 | },
587 | "react-konami-code": {
588 | "version": "file:react-konami-code-2.3.0.tgz",
589 | "integrity": "sha512-A7B8on7qqJ9RwVFqKU6DPtqVOvXJyOz2L1adwlNXD+nVWvFeMS7ru3BioaZWNYI5Ni1/LJnIQAWpgJJRFxqmIg==",
590 | "requires": {
591 | "prop-types": "^15.8.1"
592 | }
593 | },
594 | "scheduler": {
595 | "version": "0.22.0",
596 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
597 | "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
598 | "requires": {
599 | "loose-envify": "^1.1.0"
600 | }
601 | },
602 | "source-map-js": {
603 | "version": "1.0.2",
604 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
605 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
606 | },
607 | "styled-jsx": {
608 | "version": "5.0.2",
609 | "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.2.tgz",
610 | "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ=="
611 | }
612 | }
613 | }
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-konami-code-example",
3 | "description": "React Konami Code Example",
4 | "version": "1.0.0",
5 | "scripts": {
6 | "preinstall": "npx npm-force-resolutions",
7 | "dev": "next",
8 | "build": "next build",
9 | "start": "next start"
10 | },
11 | "author": "Vinicius Marchesin Araujo ",
12 | "dependencies": {
13 | "next": "12.1.6",
14 | "react": "18.1.0",
15 | "react-dom": "18.1.0",
16 | "react-konami-code": "file:react-konami-code-2.3.0.tgz"
17 | },
18 | "resolutions": {
19 | "react": "18.1.0",
20 | "react-dom": "18.1.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/example/pages/component.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Konami from 'react-konami-code';
3 |
4 | const EasterEgg = () => {
5 | return {"Hey, I'm an Easter Egg! Look at me!"}
;
6 | };
7 |
8 | export default class WithComponent extends React.Component {
9 | easterEgg = () => {
10 | alert('Hey, you typed the Konami Code!');
11 | }
12 |
13 | render = () => (
14 |
15 |
16 |
17 |
18 |
19 | Type the Konami Code:
20 | ↑ ↑ ↓ ↓ ← → ← → B A
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/example/pages/hook.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useKonami } from 'react-konami-code';
3 |
4 | const easterEgg = () => {
5 | alert('Hey, you typed the Konami Code!');
6 | };
7 |
8 | export default () => {
9 | useKonami(easterEgg);
10 | return (
11 |
12 | Type the Konami Code:
13 | ↑ ↑ ↓ ↓ ← → ← → B A
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/example/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 |
4 | export default () => (
5 |
6 |
11 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/example/react-konami-code-2.3.0.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vmarchesin/react-konami-code/36d8dd72e8be57c0af8c1d638d1961f05f00aee4/example/react-konami-code-2.3.0.tgz
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-konami-code",
3 | "version": "2.3.0",
4 | "description": "Trigger an easter egg by pressing a sequence of keys. Available as a component or a custom hook. Supports timeout and input debounce/reset.",
5 | "main": "dist/Konami.js",
6 | "types": "dist/index.d.ts",
7 | "scripts": {
8 | "prebuild": "rm -rf ./dist",
9 | "build": "webpack --mode production",
10 | "lint": "eslint src/* --ext .ts,.tsx,.d.ts",
11 | "prepublish": "npm run build",
12 | "test": "jest",
13 | "test:coverage": "jest --coverage --no-cache"
14 | },
15 | "jest": {
16 | "preset": "ts-jest",
17 | "testEnvironment": "jsdom",
18 | "setupFiles": [
19 | "./test/jestSetup.ts"
20 | ],
21 | "snapshotSerializers": [
22 | "enzyme-to-json/serializer"
23 | ],
24 | "testURL": "http://localhost/",
25 | "verbose": true
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "git+https://github.com/vmarchesin/react-konami-code.git"
30 | },
31 | "keywords": [
32 | "react",
33 | "konami",
34 | "konami code",
35 | "easter egg",
36 | "code",
37 | "konamicode",
38 | "component",
39 | "hook",
40 | "hooks",
41 | "useKonami"
42 | ],
43 | "author": "Vinicius Marchesin Araujo ",
44 | "license": "MIT",
45 | "bugs": {
46 | "url": "https://github.com/vmarchesin/react-konami-code/issues"
47 | },
48 | "homepage": "https://github.com/vmarchesin/react-konami-code#readme",
49 | "devDependencies": {
50 | "@babel/core": "^7.17.10",
51 | "@babel/preset-env": "^7.17.10",
52 | "@babel/preset-react": "^7.16.7",
53 | "@babel/preset-typescript": "^7.16.7",
54 | "@types/enzyme": "^3.10.12",
55 | "@types/enzyme-adapter-react-16": "^1.0.6",
56 | "@types/jest": "^26.0.22",
57 | "@types/react": "^18.0.9",
58 | "@typescript-eslint/eslint-plugin": "^5.22.0",
59 | "@typescript-eslint/parser": "^5.22.0",
60 | "babel-loader": "^8.2.5",
61 | "enzyme": "^3.11.0",
62 | "enzyme-adapter-react-16": "^1.15.6",
63 | "enzyme-to-json": "^3.6.2",
64 | "eslint": "^8.15.0",
65 | "eslint-config-airbnb": "^19.0.4",
66 | "eslint-plugin-jsx-a11y": "^6.5.1",
67 | "eslint-plugin-react": "^7.29.4",
68 | "jest": "^26.0.0",
69 | "react": "^18.1.0",
70 | "react-dom": "^18.1.0",
71 | "ts-jest": "^26.5.4",
72 | "ts-loader": "^8.0.18",
73 | "typescript": "^4.6.4",
74 | "webpack": "^5.72.0",
75 | "webpack-cli": "^4.9.2"
76 | },
77 | "dependencies": {
78 | "prop-types": "^15.8.1"
79 | },
80 | "peerDependencies": {
81 | "react": ">= 16.8.0",
82 | "react-dom": ">= 16.8.0"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Konami.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Timer from './utils/Timer';
5 | import arrayUtils from './utils/array';
6 | import { KONAMI_CODE } from './utils/consts';
7 |
8 | export interface KonamiProps {
9 | className?: string;
10 | code?: number[];
11 | disabled?: boolean;
12 | resetDelay?: number;
13 | action?: (() => void) | null;
14 | timeout?: number;
15 | onTimeout?: (() => void) | null;
16 | children?: React.ReactNode;
17 | }
18 |
19 | export interface KonamiState {
20 | done: boolean;
21 | input: number[];
22 | }
23 |
24 | const propTypes = {
25 | action: PropTypes.func,
26 | className: PropTypes.string,
27 | code: PropTypes.arrayOf(PropTypes.number),
28 | disabled: PropTypes.bool,
29 | onTimeout: PropTypes.func,
30 | resetDelay: PropTypes.number,
31 | timeout: PropTypes.number,
32 | };
33 |
34 | class Konami extends React.Component {
35 | private timeoutID: ReturnType | null | undefined;
36 | private _timer: any;
37 | static defaultProps: KonamiProps;
38 |
39 | static propTypes: typeof propTypes;
40 |
41 | constructor(props: KonamiProps) {
42 | super(props);
43 |
44 | this.state = {
45 | done: false,
46 | input: [],
47 | };
48 |
49 | this.timeoutID = null;
50 | this.onKeyUp = this.onKeyUp.bind(this);
51 | this.resetInput = this.resetInput.bind(this);
52 | }
53 |
54 | componentDidMount() {
55 | const { resetDelay } = this.props;
56 |
57 | document.addEventListener('keyup', this.onKeyUp);
58 | const delay = Number(resetDelay);
59 | if (delay !== 0) {
60 | this._timer = new Timer(() => this.resetInput(), delay);
61 | }
62 | }
63 |
64 | shouldComponentUpdate(nextProps: KonamiProps, nextState: KonamiState) {
65 | if (this.props.className !== nextProps.className
66 | || this.props.disabled !== nextProps.disabled
67 | ) {
68 | return true;
69 | }
70 | return this.state.done !== nextState.done;
71 | }
72 |
73 | componentWillUnmount() {
74 | const { resetDelay } = this.props;
75 |
76 | if (this.timeoutID) clearTimeout(this.timeoutID);
77 |
78 | if (resetDelay !== 0) {
79 | this._timer.stop();
80 | }
81 | document.removeEventListener('keyup', this.onKeyUp);
82 | }
83 |
84 | onKeyUp(e: KeyboardEvent) {
85 | const { done, input } = this.state;
86 | const {
87 | action,
88 | code,
89 | disabled,
90 | onTimeout,
91 | resetDelay,
92 | timeout,
93 | } = this.props;
94 |
95 | const delay = Number(resetDelay);
96 |
97 | if (disabled) {
98 | return;
99 | }
100 |
101 | if (delay !== 0) {
102 | this._timer.reset(delay);
103 | }
104 |
105 | input.push(e.keyCode);
106 |
107 | if (code) {
108 | input.splice(-code.length - 1, input.length - code.length);
109 | }
110 |
111 | this.setState({ input }, () => {
112 | if (arrayUtils.equals(this.state.input, code) && !done) { // eslint-disable-line
113 | if (delay !== 0) {
114 | this._timer.stop();
115 | }
116 | this.setState({ done: true }, () => {
117 | if (typeof action === 'function') {
118 | action();
119 | }
120 | });
121 |
122 | if (timeout) {
123 | this.timeoutID = setTimeout(() => {
124 | this.setState({ done: false });
125 | if (typeof onTimeout === 'function') {
126 | onTimeout();
127 | }
128 | }, Number(timeout));
129 | }
130 | }
131 | });
132 | }
133 |
134 | resetInput() {
135 | this.setState({ input: [] });
136 | }
137 |
138 | render() {
139 | const { children, className, disabled } = this.props;
140 | const { done } = this.state;
141 |
142 | return (
143 |
147 | {children}
148 |
149 | );
150 | }
151 | }
152 |
153 | Konami.defaultProps = {
154 | className: '',
155 | code: KONAMI_CODE,
156 | disabled: false,
157 | resetDelay: 1000,
158 | children: null,
159 | action: null,
160 | onTimeout: null,
161 | timeout: 0,
162 | };
163 |
164 | Konami.propTypes = propTypes;
165 |
166 | export default Konami;
167 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import Konami from './Konami';
2 |
3 | export default Konami;
4 | export { default as useKonami } from './useKonami';
5 |
--------------------------------------------------------------------------------
/src/useKonami.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useCallback } from 'react';
2 |
3 | import { KONAMI_CODE } from './utils/consts';
4 |
5 | function useKonami(action: () => void, { code = KONAMI_CODE } = {}) {
6 | const [input, setInput] = useState([]);
7 |
8 | const onKeyUp = useCallback(
9 | (e: KeyboardEvent) => {
10 | const newInput = input;
11 | newInput.push(e.keyCode);
12 | newInput.splice(-code.length - 1, input.length - code.length);
13 |
14 | setInput(newInput);
15 |
16 | if (newInput.join('').includes(code.join(''))) {
17 | action();
18 | }
19 | },
20 | [input, setInput, code, action],
21 | );
22 |
23 | useEffect(() => {
24 | document.addEventListener('keyup', onKeyUp);
25 | return () => {
26 | document.removeEventListener('keyup', onKeyUp);
27 | };
28 | }, [onKeyUp]);
29 | }
30 |
31 | export default useKonami;
32 |
--------------------------------------------------------------------------------
/src/utils/Timer.tsx:
--------------------------------------------------------------------------------
1 | class Timer {
2 | t: number;
3 | timerIntervalID: number | null;
4 | fn: TimerHandler;
5 |
6 | constructor(fn: () => void, t: number) {
7 | this.t = t;
8 | this.fn = fn;
9 |
10 | this.timerIntervalID = setInterval(this.fn, this.t);
11 | }
12 |
13 | stop() {
14 | if (this.timerIntervalID) {
15 | clearInterval(this.timerIntervalID);
16 | this.timerIntervalID = null;
17 | }
18 | return this;
19 | }
20 |
21 | start() {
22 | if (!this.timerIntervalID) {
23 | this.stop();
24 | this.timerIntervalID = setInterval(this.fn, this.t);
25 | }
26 | return this;
27 | }
28 |
29 | reset(newT: number) {
30 | this.t = newT;
31 | return this.stop().start();
32 | }
33 | }
34 |
35 | export default Timer;
36 |
--------------------------------------------------------------------------------
/src/utils/array.ts:
--------------------------------------------------------------------------------
1 | function equals(arr1: number[] | undefined, arr2: number[] | undefined): boolean {
2 | if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
3 | return false;
4 | }
5 |
6 | if (arr1.length !== arr2.length) {
7 | return false;
8 | }
9 |
10 | return arr1.every((value, index) => value === arr2[index]);
11 | }
12 |
13 | export default { equals };
14 |
--------------------------------------------------------------------------------
/src/utils/consts.ts:
--------------------------------------------------------------------------------
1 | export const KONAMI_CODE = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
2 |
--------------------------------------------------------------------------------
/test/Konami.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Konami from '../src';
3 | import { KONAMI_CODE } from '../src/utils/consts';
4 | import { shallow } from 'enzyme';
5 |
6 | describe('Test cases', () => {
7 | it('should render default component', () => {
8 | const wrapper = shallow(
9 | {"Hey, I'm an Easter Egg!"}
10 | );
11 | expect(wrapper).toMatchSnapshot();
12 | });
13 |
14 | it('should add a className to the wrapping div', () => {
15 | const wrapper = shallow(
16 | {"Hey, I'm an Easter Egg!"}
17 | );
18 | expect(wrapper).toMatchSnapshot();
19 | });
20 |
21 | it('should not be done after wrong code input', () => {
22 | const wrapper = shallow(
23 | {"Hey, I'm an Easter Egg!"}
24 | );
25 |
26 | const instance = wrapper.instance();
27 | const input = [38, 37, 40, 40, 37, 39, 37, 39, 66, 65];
28 | input.forEach((keyCode) => {
29 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode }));
30 | });
31 |
32 | expect(wrapper.state('done')).toEqual(false);
33 | });
34 |
35 | it('should be done after code input', () => {
36 | const cb = jest.fn();
37 | const wrapper = shallow(
38 | {"Hey, I'm an Easter Egg!"}
39 | );
40 |
41 | const instance = wrapper.instance();
42 | const input = KONAMI_CODE;
43 | input.forEach((keyCode) => {
44 | const event = new KeyboardEvent('keyup', { keyCode });
45 | instance.onKeyUp(event);
46 | });
47 |
48 | expect(wrapper.state('done')).toEqual(true);
49 | expect(cb).toHaveBeenCalledTimes(1);
50 | });
51 |
52 | it('should not be done after code input while disabled', () => {
53 | const wrapper = shallow(
54 | {"Hey, I'm an Easter Egg!"}
55 | );
56 |
57 | const instance = wrapper.instance();
58 | const input = KONAMI_CODE;
59 | input.forEach((keyCode) => {
60 | const event = new KeyboardEvent('keyup', { keyCode });
61 | instance.onKeyUp(event);
62 | });
63 |
64 | expect(wrapper.state('done')).toEqual(false);
65 | });
66 |
67 | it('should be done after custom code input', () => {
68 | const wrapper = shallow(
69 | {"Hey, I'm an Easter Egg!"}
70 | );
71 |
72 | const instance = wrapper.instance();
73 |
74 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
75 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
76 |
77 | expect(wrapper.state('done')).toEqual(true);
78 | });
79 |
80 | it('should not be done after timeout', (done) => {
81 | const onEnd = jest.fn();
82 | const wrapper = shallow(
83 |
84 | {"Hey, I'm an Easter Egg!"}
85 |
86 | );
87 |
88 | const instance = wrapper.instance();
89 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
90 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
91 |
92 | setTimeout(() => {
93 | expect(wrapper.state('done')).toEqual(false);
94 | expect(onEnd).toHaveBeenCalledTimes(1);
95 | done();
96 | }, 1000);
97 | });
98 |
99 | it('should not be done if you are slower than resetDelay', (done) => {
100 | const wrapper = shallow(
101 | {"Hey, I'm an Easter Egg!"}
102 | );
103 |
104 | const instance = wrapper.instance();
105 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
106 |
107 | setTimeout(() => {
108 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
109 | expect(wrapper.state('done')).toEqual(false);
110 | done();
111 | }, 1000);
112 | });
113 |
114 | it('should be done if you are slow but resetDelay is zero', (done) => {
115 | const wrapper = shallow(
116 |
117 | {"Hey, I'm an Easter Egg!"}
118 |
119 | );
120 |
121 | const instance = wrapper.instance();
122 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
123 |
124 | setTimeout(() => {
125 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
126 | expect(wrapper.state('done')).toEqual(true);
127 | done();
128 | }, 1000);
129 | });
130 |
131 | it('should reset input if user is slow', (done) => {
132 | const wrapper = shallow(
133 | {"Hey, I'm an Easter Egg!"}
134 | );
135 |
136 | const instance = wrapper.instance();
137 | instance.onKeyUp(new KeyboardEvent('keyup', { keyCode: 55 }));
138 |
139 | setTimeout(() => {
140 | expect(wrapper.state('input')).toEqual([]);
141 | done();
142 | }, 1000);
143 | });
144 |
145 | it('should unmount component', () => {
146 | const wrapper = shallow(
147 | {"Hey, I'm an Easter Egg!"}
148 | );
149 |
150 | const instance = wrapper.instance();
151 | const componentWillUnmount = jest.spyOn(instance, 'componentWillUnmount');
152 | wrapper.unmount();
153 | expect(componentWillUnmount).toHaveBeenCalled();
154 | });
155 | });
156 |
--------------------------------------------------------------------------------
/test/__snapshots__/Konami.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Test cases should add a className to the wrapping div 1`] = `
4 |
12 | Hey, I'm an Easter Egg!
13 |
14 | `;
15 |
16 | exports[`Test cases should render default component 1`] = `
17 |
25 | Hey, I'm an Easter Egg!
26 |
27 | `;
28 |
--------------------------------------------------------------------------------
/test/jestSetup.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 |
5 | Enzyme.configure({ adapter: new Adapter() });
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "ESNext",
5 | "jsx": "react",
6 | "declaration": true,
7 | "outDir": "./dist",
8 | "strict": true,
9 | "noImplicitAny": true,
10 | "strictNullChecks": true,
11 | "strictFunctionTypes": true,
12 | "strictBindCallApply": true,
13 | "strictPropertyInitialization": true,
14 | "noImplicitThis": true,
15 | "alwaysStrict": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "noImplicitReturns": true,
19 | "esModuleInterop": true,"skipLibCheck": true,
20 | "forceConsistentCasingInFileNames": true
21 | },
22 | "include": ["./src/*"],
23 | "exclude": ["./node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'production',
5 | entry: './src/index.ts',
6 | output: {
7 | path: path.resolve('dist'),
8 | filename: 'Konami.js',
9 | libraryTarget: 'commonjs2',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.(js|ts)x?$/,
15 | exclude: /(node_modules)/,
16 | use: {
17 | loader: 'babel-loader',
18 | options: {
19 | presets: [
20 | '@babel/preset-env',
21 | '@babel/react',
22 | '@babel/preset-typescript',
23 | ],
24 | },
25 | },
26 | },
27 | {
28 | loader: 'ts-loader',
29 | },
30 | ],
31 | },
32 | resolve: {
33 | extensions: ['.tsx', '.ts'],
34 | },
35 | externals: [
36 | {
37 | react: {
38 | root: 'React',
39 | commonjs2: 'react',
40 | commonjs: 'react',
41 | amd: 'react',
42 | },
43 | 'react-dom': {
44 | root: 'ReactDOM',
45 | commonjs2: 'react-dom',
46 | commonjs: 'react-dom',
47 | amd: 'react-dom',
48 | },
49 | 'prop-types': {
50 | root: 'PropTypes',
51 | commonjs2: 'prop-types',
52 | commonjs: 'prop-types',
53 | amd: 'prop-types',
54 | },
55 | },
56 | ],
57 | };
58 |
--------------------------------------------------------------------------------