Skip to content

Commit 9b81513

Browse files
committed
Initial commit
1 parent 40ccbbc commit 9b81513

File tree

6 files changed

+281
-0
lines changed

6 files changed

+281
-0
lines changed

.codesandbox/workspace.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"responsive-preview": {
3+
"Mobile": [
4+
320,
5+
675
6+
],
7+
"Tablet": [
8+
1024,
9+
765
10+
],
11+
"Desktop": [
12+
1400,
13+
800
14+
],
15+
"Desktop HD": [
16+
1920,
17+
1080
18+
]
19+
}
20+
}

package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "react",
3+
"version": "1.0.0",
4+
"description": "React example starter project",
5+
"keywords": ["react", "starter"],
6+
"main": "src/index.js",
7+
"dependencies": {
8+
"react": "17.0.2",
9+
"react-dom": "17.0.2",
10+
"react-scripts": "4.0.0"
11+
},
12+
"devDependencies": {
13+
"@babel/runtime": "7.13.8",
14+
"typescript": "4.1.3"
15+
},
16+
"scripts": {
17+
"start": "react-scripts start",
18+
"build": "react-scripts build",
19+
"test": "react-scripts test --env=jsdom",
20+
"eject": "react-scripts eject"
21+
},
22+
"browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"]
23+
}

public/index.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7+
<meta name="theme-color" content="#000000">
8+
<!--
9+
manifest.json provides metadata used when your web app is added to the
10+
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
11+
-->
12+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
13+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
14+
<!--
15+
Notice the use of %PUBLIC_URL% in the tags above.
16+
It will be replaced with the URL of the `public` folder during the build.
17+
Only files inside the `public` folder can be referenced from the HTML.
18+
19+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
20+
work correctly both with client-side routing and a non-root public URL.
21+
Learn how to configure a non-root public URL by running `npm run build`.
22+
-->
23+
<title>React App</title>
24+
</head>
25+
26+
<body>
27+
<noscript>
28+
You need to enable JavaScript to run this app.
29+
</noscript>
30+
<div id="root"></div>
31+
<!--
32+
This HTML file is a template.
33+
If you open it directly in the browser, you will see an empty page.
34+
35+
You can add webfonts, meta tags, or analytics to this file.
36+
The build step will place the bundled scripts into the <body> tag.
37+
38+
To begin the development, run `npm start` or `yarn start`.
39+
To create a production bundle, use `npm run build` or `yarn build`.
40+
-->
41+
</body>
42+
43+
</html>

src/App.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// https://www.notion.so/Frontend-e12411d8ddab4184b47efb6158a839d1
2+
3+
import { useEffect, useRef, useState } from "react";
4+
import "./styles.css";
5+
6+
function moveElement(originalArr, fromIndex, toIndex) {
7+
const arr = originalArr.slice();
8+
const element = arr[fromIndex];
9+
arr.splice(fromIndex, 1);
10+
arr.splice(toIndex, 0, element);
11+
return arr;
12+
}
13+
14+
const CURSOR_CHAR = "CURSOR_CHAR";
15+
16+
const Cursor = () => <span className="blinking-cursor">|</span>;
17+
18+
const Input = () => {
19+
const inputRef = useRef();
20+
const [value, setValue] = useState([CURSOR_CHAR]);
21+
const [isCursorShown, setIsCursorShown] = useState(false);
22+
23+
useEffect(() => {
24+
const handleKeyPress = (ev) => {
25+
if (document.activeElement !== inputRef.current) {
26+
return;
27+
}
28+
29+
const nextValue = value.slice();
30+
const cursorIdx = nextValue.findIndex((v) => v === CURSOR_CHAR);
31+
32+
switch (ev.key) {
33+
case "Backspace":
34+
if (nextValue.length !== 1) {
35+
nextValue.splice(cursorIdx - 1, 1);
36+
setValue(nextValue);
37+
}
38+
break;
39+
case "Delete":
40+
if (nextValue.length !== 1) {
41+
nextValue.splice(cursorIdx + 1, 1);
42+
setValue(nextValue);
43+
}
44+
break;
45+
case "ArrowRight":
46+
if (cursorIdx === value.length - 1) {
47+
return;
48+
}
49+
setValue(moveElement(nextValue, cursorIdx, cursorIdx + 1));
50+
break;
51+
case "ArrowLeft":
52+
if (cursorIdx === 0) {
53+
return;
54+
}
55+
setValue(moveElement(nextValue, cursorIdx, cursorIdx - 1));
56+
break;
57+
case "Enter":
58+
break;
59+
default:
60+
nextValue.splice(cursorIdx, 0, ev.key);
61+
setValue(nextValue);
62+
}
63+
};
64+
65+
const handleClick = (ev) => {
66+
setIsCursorShown(document.activeElement === inputRef.current);
67+
};
68+
69+
window.addEventListener("keydown", handleKeyPress);
70+
window.addEventListener("click", handleClick);
71+
72+
return () => {
73+
window.removeEventListener("keydown", handleKeyPress);
74+
window.addEventListener("click", handleClick);
75+
};
76+
}, [value, setValue]);
77+
78+
return (
79+
<div className="Input" ref={inputRef} tabIndex="0">
80+
{value.map((char, idx) => {
81+
if (char === CURSOR_CHAR) {
82+
return isCursorShown ? <Cursor key={char + idx} /> : null;
83+
}
84+
return (
85+
<span key={char + idx} data-key={char}>
86+
{char}
87+
</span>
88+
);
89+
})}
90+
</div>
91+
);
92+
};
93+
94+
export default function App() {
95+
return (
96+
<div className="App">
97+
<Input />
98+
<h1>Hello CodeSandbox</h1>
99+
<h2>Start editing to see some magic happen!</h2>
100+
</div>
101+
);
102+
}

src/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { StrictMode } from "react";
2+
import ReactDOM from "react-dom";
3+
4+
import App from "./App";
5+
6+
const rootElement = document.getElementById("root");
7+
ReactDOM.render(
8+
<StrictMode>
9+
<App />
10+
</StrictMode>,
11+
rootElement
12+
);

src/styles.css

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
.App {
2+
font-family: sans-serif;
3+
text-align: center;
4+
}
5+
6+
.Input {
7+
border: 1px solid gray;
8+
min-height: 24px;
9+
border-radius: 5px;
10+
display: flex;
11+
align-items: center;
12+
}
13+
14+
.Input:focus {
15+
box-shadow: 0 0 3px blue;
16+
}
17+
18+
.Input span[data-key=" "] {
19+
width: 3px;
20+
}
21+
22+
.blinking-cursor {
23+
font-weight: 100;
24+
font-size: 20px;
25+
color: #2e3d48;
26+
-webkit-animation: 1s blink step-end infinite;
27+
-moz-animation: 1s blink step-end infinite;
28+
-ms-animation: 1s blink step-end infinite;
29+
-o-animation: 1s blink step-end infinite;
30+
animation: 1s blink step-end infinite;
31+
}
32+
33+
@keyframes "blink" {
34+
from,
35+
to {
36+
color: transparent;
37+
}
38+
50% {
39+
color: black;
40+
}
41+
}
42+
43+
@-moz-keyframes blink {
44+
from,
45+
to {
46+
color: transparent;
47+
}
48+
50% {
49+
color: black;
50+
}
51+
}
52+
53+
@-webkit-keyframes "blink" {
54+
from,
55+
to {
56+
color: transparent;
57+
}
58+
50% {
59+
color: black;
60+
}
61+
}
62+
63+
@-ms-keyframes "blink" {
64+
from,
65+
to {
66+
color: transparent;
67+
}
68+
50% {
69+
color: black;
70+
}
71+
}
72+
73+
@-o-keyframes "blink" {
74+
from,
75+
to {
76+
color: transparent;
77+
}
78+
50% {
79+
color: black;
80+
}
81+
}

0 commit comments

Comments
 (0)