├── README.md ├── vite.config.js ├── src ├── App.jsx ├── main.jsx ├── App.css ├── index.css ├── components │ └── Opt.jsx └── assets │ └── react.svg ├── .gitignore ├── index.html ├── .eslintrc.cjs ├── package.json └── public └── vite.svg /README.md: -------------------------------------------------------------------------------- 1 | # Otp 2 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | import Opt from './components/Opt' 3 | 4 | function App() { 5 | 6 | return ( 7 | <> 8 |
9 |

OTP

10 | 11 |
12 | 13 | ) 14 | } 15 | 16 | export default App 17 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | height: 100vh; 6 | margin: 0; 7 | } 8 | 9 | input { 10 | height: 1rem; 11 | width: 1rem; 12 | padding: 0.5rem; 13 | margin: 0.5rem; 14 | } 15 | 16 | .container { 17 | height: 50vh; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otp-create", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.3.3", 18 | "@types/react-dom": "^18.3.0", 19 | "@vitejs/plugin-react": "^4.3.1", 20 | "eslint": "^8.57.0", 21 | "eslint-plugin-react": "^7.34.2", 22 | "eslint-plugin-react-hooks": "^4.6.2", 23 | "eslint-plugin-react-refresh": "^0.4.7", 24 | "vite": "^5.3.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Opt.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import '../App.css'; 3 | 4 | const Opt = ({ otpLength = 6 }) => { 5 | const [otpFields, setOtpFields] = useState(new Array(otpLength).fill("")); 6 | const ref = useRef([]); 7 | 8 | const handleKeyDown = (e, index) => { 9 | const key = e.key; 10 | 11 | if (key === 'ArrowLeft' && index > 0) { 12 | e.preventDefault(); 13 | ref.current[index - 1]?.focus(); 14 | } 15 | if (key === "ArrowRight" && index + 1 < otpFields.length) { 16 | e.preventDefault(); 17 | ref.current[index + 1]?.focus(); 18 | } 19 | const copyOtpFields = [...otpFields]; 20 | if (key === "Backspace") { 21 | e.preventDefault(); 22 | copyOtpFields[index] = ""; 23 | setOtpFields(copyOtpFields); 24 | if (index > 0) ref.current[index - 1]?.focus(); 25 | return; 26 | } 27 | if (isNaN(key)) { 28 | e.preventDefault(); 29 | return; 30 | } 31 | e.preventDefault(); 32 | copyOtpFields[index] = key; 33 | setOtpFields(copyOtpFields); 34 | if (index + 1 < otpFields.length) { 35 | ref.current[index + 1]?.focus(); 36 | } 37 | }; 38 | 39 | const handleChange = (e, index) => { 40 | const value = e.target.value; 41 | const copyOtpFields = [...otpFields]; 42 | if (!isNaN(value) && value.length === 1) { 43 | copyOtpFields[index] = value; 44 | setOtpFields(copyOtpFields); 45 | if (index + 1 < otpFields.length) { 46 | ref.current[index + 1]?.focus(); 47 | } 48 | } 49 | }; 50 | 51 | useEffect(() => { 52 | ref.current[0]?.focus(); 53 | }, []); 54 | 55 | return ( 56 |
57 | {otpFields.map((value, index) => ( 58 | (ref.current[index] = currentInput)} 61 | type="text" 62 | value={value} 63 | onKeyDown={(e) => handleKeyDown(e, index)} 64 | onChange={(e) => handleChange(e, index)} 65 | /> 66 | ))} 67 |
68 | ); 69 | }; 70 | 71 | export default Opt; 72 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------