├── .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 [![GitHub stars](https://img.shields.io/github/stars/vmarchesin/react-konami-code.svg?style=social&label=Star&maxAge=2592000)](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 | [![npm](https://img.shields.io/npm/v/react-konami-code.svg)]() 6 | [![npm](https://img.shields.io/npm/dt/react-konami-code.svg)]() 7 | [![gzip size](http://img.badgesize.io/https://npmcdn.com/react-konami-code/dist/Konami.js?compression=gzip)]() 8 | ![Travis](https://travis-ci.org/vmarchesin/react-konami-code.svg?branch=master) 9 | ![Maintenance](https://img.shields.io/maintenance/yes/2022.svg) 10 | [![Known Vulnerabilities](https://snyk.io/test/github/vmarchesin/react-konami-code/badge.svg)](https://snyk.io/test/github/vmarchesin/react-konami-code) 11 | 12 | ## Install 13 | 14 | [![NPM](https://nodei.co/npm/react-konami-code.png)](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 |
7 | 8 | With Component 9 | 10 |
11 |
12 | 13 | With Custom Hook 14 | 15 |
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 | --------------------------------------------------------------------------------