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;
+ }
+}