├── 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 |
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 |
--------------------------------------------------------------------------------