From e2a9e6866635ca31fd6d5301623bc4da03841ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:57:18 +0300 Subject: [PATCH] code editor --- package-lock.json | 59 +++++++++ package.json | 2 + src/App.tsx | 4 +- src/assets/icons/input/index.ts | 4 +- src/assets/icons/input/upload.svg | 3 + src/components/codeeditor/CodeEditor.tsx | 125 ++++++++++++++++++ .../drop-down-list/DropDownList.tsx | 78 +++++++++++ src/components/input/Input.tsx | 4 +- 8 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 src/assets/icons/input/upload.svg create mode 100644 src/components/codeeditor/CodeEditor.tsx create mode 100644 src/components/drop-down-list/DropDownList.tsx diff --git a/package-lock.json b/package-lock.json index 8b747af..799d483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,13 @@ "name": "react-kit", "version": "0.0.0", "dependencies": { + "@monaco-editor/react": "^4.7.0", "@reduxjs/toolkit": "^2.9.2", "@types/react-redux": "^7.1.33", "axios": "^1.12.2", "clsx": "^2.1.1", "framer-motion": "^11.9.0", + "monaco-editor": "^0.54.0", "postcss": "^8.4.47", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1022,6 +1024,29 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@monaco-editor/loader": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.6.1.tgz", + "integrity": "sha512-w3tEnj9HYEC73wtjdpR089AqkUPskFRcdkxsiSFt3SoUc3OHpmu+leP94CXBm4mHfefmhsdfI0ZQu6qJ0wgtPg==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2181,6 +2206,12 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dompurify": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz", + "integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==", + "license": "(MPL-2.0 OR Apache-2.0)" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3315,6 +3346,18 @@ "yallist": "^3.0.2" } }, + "node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3389,6 +3432,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/monaco-editor": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.54.0.tgz", + "integrity": "sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==", + "license": "MIT", + "dependencies": { + "dompurify": "3.1.7", + "marked": "14.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4142,6 +4195,12 @@ "node": ">=0.10.0" } }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", diff --git a/package.json b/package.json index df92240..33cd567 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,13 @@ "preview": "vite preview" }, "dependencies": { + "@monaco-editor/react": "^4.7.0", "@reduxjs/toolkit": "^2.9.2", "@types/react-redux": "^7.1.33", "axios": "^1.12.2", "clsx": "^2.1.1", "framer-motion": "^11.9.0", + "monaco-editor": "^0.54.0", "postcss": "^8.4.47", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/App.tsx b/src/App.tsx index 65240b0..d02b174 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,12 +5,14 @@ import { Route, Routes } from "react-router-dom"; // import { Input } from "./components/input/Input"; // import { Switch } from "./components/switch/Switch"; import Home from "./pages/Home"; +import CodeEditor from "./components/codeeditor/CodeEditor"; function App() { return ( -
+
}/> +
}/> }/> diff --git a/src/assets/icons/input/index.ts b/src/assets/icons/input/index.ts index d8a755a..b9f6e60 100644 --- a/src/assets/icons/input/index.ts +++ b/src/assets/icons/input/index.ts @@ -1,5 +1,7 @@ import eyeClosed from "./eye-closed.svg" import eyeOpen from "./eye-open.png" import googleLogo from "./google-logo.svg" +import upload from "./upload.svg" -export {eyeClosed, eyeOpen, googleLogo} \ No newline at end of file + +export {eyeClosed, eyeOpen, googleLogo, upload} \ No newline at end of file diff --git a/src/assets/icons/input/upload.svg b/src/assets/icons/input/upload.svg new file mode 100644 index 0000000..86f59a5 --- /dev/null +++ b/src/assets/icons/input/upload.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/codeeditor/CodeEditor.tsx b/src/components/codeeditor/CodeEditor.tsx new file mode 100644 index 0000000..2987a40 --- /dev/null +++ b/src/components/codeeditor/CodeEditor.tsx @@ -0,0 +1,125 @@ +import React, { useState } from "react"; +import Editor from "@monaco-editor/react"; +import { upload } from "../../assets/icons/input"; +import { cn } from "../../lib/cn"; + +const languageMap: Record = { + c: "cpp", + cpp: "cpp", + java: "java", + python: "python", + pascal: "pascal", +}; + +const CodeEditor: React.FC = () => { + const [language, setLanguage] = useState("cpp"); + const [code, setCode] = useState(""); + const [isDragging, setIsDragging] = useState(false); + + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + const text = event.target?.result; + if (typeof text === "string") setCode(text); + }; + reader.readAsText(file); + e.target.value = ""; + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + const droppedFile = e.dataTransfer.files[0]; + if (!droppedFile) return; + + const reader = new FileReader(); + reader.onload = (event) => { + const text = event.target?.result; + if (typeof text === "string") setCode(text); + }; + reader.readAsText(droppedFile); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); // обязательно + }; + + const handleDragEnter = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + }; + + return ( +
+ {/* Панель выбора языка и загрузки файла */} +
+
+ + + +
+
+ + {/* Monaco Editor */} +
+ setCode(value ?? "")} + theme="vs-dark" + options={{ + fontSize: 14, + minimap: { enabled: false }, + automaticLayout: true, + quickSuggestions: true, + suggestOnTriggerCharacters: true, + tabSize: 4, + insertSpaces: true, + detectIndentation: false, + autoIndent: "full", + }} + /> +
+
+ ); +}; + +export default CodeEditor; diff --git a/src/components/drop-down-list/DropDownList.tsx b/src/components/drop-down-list/DropDownList.tsx new file mode 100644 index 0000000..e70ccfa --- /dev/null +++ b/src/components/drop-down-list/DropDownList.tsx @@ -0,0 +1,78 @@ +import React from "react"; +import { cn } from "../../lib/cn"; +import { eyeClosed, eyeOpen } from "../../assets/icons/input"; + +interface DwopDownListProps { + name?: string; + type: "text" | "email" | "password" | "first_name"; + error?: string; + disabled?: boolean; + required?: boolean; + label?: string; + placeholder?: string; + className?: string; + onChange: (state: string) => void; + defaultState?: string; + autocomplete?: string; +} + +export const DwopDownList: React.FC = ({ + type = "text", + error = "", +// disabled = false, +// required = false, + label = "", + placeholder = "", + className = "", + onChange, + defaultState = "", + name = "", + autocomplete="", +}) => { + const [value, setValue] = React.useState(defaultState); + const [visible, setVIsible] = React.useState(type != "password"); + + React.useEffect(() => onChange(value), [value]); + + + return ( +
+
+ {label} +
+
+ { + setValue(e.target.value); + }} /> + { + type == "password" && + { + setVIsible(!visible); + }}/> + } +
+ +
+ {error} +
+ +
+ ); + +}; diff --git a/src/components/input/Input.tsx b/src/components/input/Input.tsx index 114a5f0..1b441f0 100644 --- a/src/components/input/Input.tsx +++ b/src/components/input/Input.tsx @@ -19,8 +19,8 @@ interface inputProps { export const Input: React.FC = ({ type = "text", error = "", - disabled = false, - required = false, + // disabled = false, + // required = false, label = "", placeholder = "", className = "",