From c353f48259115376c2ec26c288684c1fca7a3e91 Mon Sep 17 00:00:00 2001 From: misterkirill Date: Sat, 5 Jul 2025 17:31:01 +0500 Subject: [PATCH] feat: implement editor state management with useReducer --- src/app/(editor)/page.tsx | 17 +---- src/components/Block.tsx | 73 +++++++++++++++++++ src/components/Editor.tsx | 33 +++++++++ src/components/LineInputGroup.tsx | 32 --------- src/lib/editorReducer.ts | 115 ++++++++++++++++++++++++++++++ 5 files changed, 223 insertions(+), 47 deletions(-) create mode 100644 src/components/Block.tsx create mode 100644 src/components/Editor.tsx delete mode 100644 src/components/LineInputGroup.tsx create mode 100644 src/lib/editorReducer.ts diff --git a/src/app/(editor)/page.tsx b/src/app/(editor)/page.tsx index 903690c..a86dd04 100644 --- a/src/app/(editor)/page.tsx +++ b/src/app/(editor)/page.tsx @@ -1,22 +1,9 @@ -"use client"; - -import { Plus } from "lucide-react"; -import { ChangeEvent } from "react"; -import LineInputGroup from "@/components/LineInputGroup"; -import IconOnlyButton from "@/components/IconOnlyButton"; +import Editor from "@/components/Editor"; export default function Home() { - const saveChanges = (e: ChangeEvent) => { - // TODO - }; - return (
-
- - - } /> -
+
); } diff --git a/src/components/Block.tsx b/src/components/Block.tsx new file mode 100644 index 0000000..eb06d88 --- /dev/null +++ b/src/components/Block.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { Lock, LockOpen, Menu, Minus, Plus, X } from "lucide-react"; +import { Action, IBlock, ILine } from "@/lib/editorReducer"; +import LineInput from "./LineInput"; +import IconOnlyButton from "./IconOnlyButton"; +import { ChangeEvent } from "react"; + +export default function Block({ + block, + dispatch, +}: { + block: IBlock; + dispatch: React.Dispatch; +}) { + const handleAddLine = () => { + dispatch({ type: "add_line", blockId: block.id }); + } + + const handleDeleteLine = () => { + dispatch({ type: "delete_line", blockId: block.id }); + } + + const handleTagUpdate = (e: ChangeEvent) => { + dispatch({ type: "update_tag", blockId: block.id, tag: e.target.value }); + } + + const handleLineUpdate = (e: ChangeEvent, line: ILine) => { + dispatch({ type: "update_line_text", blockId: block.id, lineId: line.id, text: e.target.value }); + } + + const handleDeleteBlock = () => { + dispatch({ type: "delete_block", blockId: block.id }); + } + + const handleToggleLock = () => { + dispatch({ type: "toggle_lock", blockId: block.id }); + } + + return ( +
+
+ + {block.lines.map((line) => ( + handleLineUpdate(e, line)} + /> + ))} +
+
+ } /> + } /> +
+
+ } /> + : } alwaysOn={block.locked} /> + } /> +
+
+
+
+ ); +} diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx new file mode 100644 index 0000000..7684ebf --- /dev/null +++ b/src/components/Editor.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useReducer } from "react"; +import { editorReducer } from "@/lib/editorReducer"; +import Block from "./Block"; +import IconOnlyButton from "./IconOnlyButton"; +import { Plus } from "lucide-react"; + +export default function Editor() { + const [state, dispatch] = useReducer(editorReducer, [ + { + id: crypto.randomUUID(), + tag: "", + locked: false, + lines: Array.from({ length: 4 }, () => ({ + id: crypto.randomUUID(), + text: "", + })), + } + ]); + + const handleAddBlock = () => { + dispatch({ type: "add_block" }); + } + + return ( +
+ + {state.map((block) => )} + } /> +
+ ); +} diff --git a/src/components/LineInputGroup.tsx b/src/components/LineInputGroup.tsx deleted file mode 100644 index cfe282e..0000000 --- a/src/components/LineInputGroup.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; - -import { Lock, LockOpen, Menu, X } from "lucide-react"; -import LineInput from "./LineInput"; -import IconOnlyButton from "./IconOnlyButton"; -import { ChangeEvent, useState } from "react"; - -export default function LineInputGroup({ - size = 4, - onChange, -}: { - size: number; - onChange: (e: ChangeEvent) => void; -}) { - const [locked, setLocked] = useState(false); - - const switchLock = () => setLocked((locked) => !locked); - - return ( -
-
- - {[...Array(size)].map((_, i) => )} -
-
- } /> - : } onClick={switchLock} /> - } /> -
-
- ); -} diff --git a/src/lib/editorReducer.ts b/src/lib/editorReducer.ts new file mode 100644 index 0000000..8cd7004 --- /dev/null +++ b/src/lib/editorReducer.ts @@ -0,0 +1,115 @@ +export type ILine = { + id: string; + text: string; +}; + +export type IBlock = { + id: string; + tag: string; + locked: boolean; + lines: ILine[]; +}; + +type EditorState = IBlock[]; + +export type Action = + | { type: "add_block" } + | { type: "delete_block", blockId: string } + | { type: "add_line", blockId: string } + | { type: "delete_line", blockId: string } + | { type: "update_line_text"; blockId: string; lineId: string; text: string } + | { type: "update_tag"; blockId: string; tag: string } + | { type: "toggle_lock", blockId: string }; + +export function editorReducer(state: EditorState, action: Action): EditorState { + switch (action.type) { + case "add_block": + return [ + ...state, + { + id: crypto.randomUUID(), + tag: "", + locked: false, + lines: Array.from({ length: 4 }, () => ({ + id: crypto.randomUUID(), + text: "", + })), + } + ]; + + case "delete_block": + return state.filter((block) => block.id !== action.blockId); + + case "add_line": + return state.map((block) => { + if (block.id === action.blockId) { + return { + ...block, + lines: [...block.lines, { id: crypto.randomUUID(), text: "" }], + }; + } else { + return block; + } + }); + + case "delete_line": + return state.map((block) => { + if (block.id === action.blockId && block.lines.length > 0) { + return { + ...block, + lines: block.lines.slice(0, -1), + }; + } else { + return block; + } + }); + + case "update_line_text": + return state.map((block) => { + if (block.id === action.blockId) { + return { + ...block, + lines: block.lines.map((line) => { + if (line.id === action.lineId) { + return { + ...line, + text: action.text, + }; + } else { + return line; + } + }), + }; + } else { + return block; + } + }); + + case "update_tag": + return state.map((block) => { + if (block.id === action.blockId) { + return { + ...block, + tag: action.tag, + }; + } else { + return block; + } + }); + + case "toggle_lock": + return state.map((block) => { + if (block.id === action.blockId) { + return { + ...block, + locked: !block.locked, + }; + } else { + return block; + } + }); + + default: + return state; + } +}