From b262c5a865fb21e2446898893d748dd60e862d1a Mon Sep 17 00:00:00 2001 From: YujinKang1024 Date: Wed, 12 Mar 2025 17:19:08 +0900 Subject: [PATCH] =?UTF-8?q?Ticket=20=EA=B4=80=EB=A6=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 2 +- src/d.ts | 5 ++ src/main.tsx | 138 ++++++++++++++++++++++++++++++++++++++++++++++- src/utils/jsx.ts | 27 ++++++++++ styles.css | 50 +++++++++++++++++ tsconfig.json | 14 ++--- vite.config.ts | 7 +++ 7 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 src/d.ts create mode 100644 src/utils/jsx.ts create mode 100644 vite.config.ts diff --git a/.editorconfig b/.editorconfig index 3920760..193bac6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,4 +4,4 @@ insert_final_newline = true end_of_line = lf indent_style = space indent_size = 2 -max_line_length = 80 +max_line_length = 120 diff --git a/src/d.ts b/src/d.ts new file mode 100644 index 0000000..c39c2cf --- /dev/null +++ b/src/d.ts @@ -0,0 +1,5 @@ +declare namespace JSX { + interface IntrinsicElements { + [elemName: string]: any; + } +} diff --git a/src/main.tsx b/src/main.tsx index 7830e88..2ef906f 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,4 +1,138 @@ -const root = document.getElementById('root'); +import { createElement } from "./utils/jsx"; + +interface Ticket { + id: number; + title: string; + description: string; + status: "open" | "closed"; + toggle(): void; +} + +function Header() { + return ( +
+

Tickets

+
+ ); +} + +function Main({ + tickets, + addTicket, +}: { + tickets: Ticket[]; + addTicket: ({ title, description }: { title: string; description: string }) => void; +}) { + return ( +
+ + +
+ ); +} + +function TicketList({ tickets }: { tickets: Ticket[] }) { + return ( + + ); +} + +function TicketItem({ ticket }: { ticket: Ticket }) { + const handleClick = () => { + ticket.toggle(); + }; + + return ( +
  • +
    {ticket.title}
    +
    {ticket.description}
    + +
  • + ); +} + +function TicketForm({ + addTicket, +}: { + addTicket: ({ title, description }: { title: string; description: string }) => void; +}) { + const handleSubmit = (event: Event) => { + event.preventDefault(); + + const form = event.target as HTMLFormElement; + const formData = new FormData(form); + const title = formData.get("title") as string; + const description = formData.get("description") as string; + + addTicket({ title, description }); + }; + + return ( +
    +
    + + +
    +
    + + +
    + +
    + ); +} + +function render({ + root, + tickets, + addTicket, +}: { + root: HTMLElement; + tickets: Ticket[]; + addTicket: ({ title, description }: { title: string; description: string }) => void; +}) { + root.replaceChildren( +
    +
    +
    +
    + ); +} + +const root = document.getElementById("root"); + if (root) { - root.innerHTML = '

    Hello, world!

    '; + const tickets: Ticket[] = []; + + const update = () => { + render({ root, tickets, addTicket }); + }; + + const addTicket = ({ title, description }: { title: string; description: string }) => { + const id = Math.max(...tickets.map((ticket) => ticket.id), 0) + 1; + const ticket: Ticket = { + id, + title, + description, + status: "open", + toggle() { + this.status = this.status === "open" ? "closed" : "open"; + update(); + }, + }; + + tickets.push(ticket); + + update(); + }; + + update(); } diff --git a/src/utils/jsx.ts b/src/utils/jsx.ts new file mode 100644 index 0000000..f492e25 --- /dev/null +++ b/src/utils/jsx.ts @@ -0,0 +1,27 @@ +export function createElement(type: string | Function, props: any, ...children: any[]) { + if (typeof type === "function") { + return type({ ...props, children }); + } + + const element = document.createElement(type); + Object.assign(element, props); + + if (props) { + Object.entries(props).forEach(([key, value]) => { + if (key.startsWith("on") && typeof value === "function") { + const event = key.slice(2).toLocaleLowerCase(); + element.addEventListener(event, value as EventListener); + } + }); + } + + children.forEach((child) => { + if (Array.isArray(child)) { + child.forEach((childItem) => element.append(childItem)); + return; + } + element.append(child); + }); + + return element; +} diff --git a/styles.css b/styles.css index 303c8b6..478c865 100644 --- a/styles.css +++ b/styles.css @@ -15,3 +15,53 @@ html { body { font-size: 1.6rem; } + +#ticket-list { + margin: 0; + padding: 0; + list-style: none; + + li { + margin-block: 1rem; + padding: 1rem; + border: 1px solid #ccc; + + .title { + font-size: 2rem; + font-weight: 700; + } + + .description { + margin-block: 0.5rem; + } + } +} + +form { + margin-block: 2rem; + max-width: 40rem; + + div { + margin-block: 1rem; + } + + label { + display: block; + margin-bottom: 0.4rem; + } + + input, + textarea { + width: 100%; + padding: 0.8rem 1rem; + } + + textarea { + height: 10rem; + } + + button { + width: 100%; + padding: 0.8rem 1rem; + } +} diff --git a/tsconfig.json b/tsconfig.json index 0760e96..ee047e5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,9 +11,9 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "ES2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - "jsx": "preserve", /* Specify what JSX code is generated. */ + "jsx": "preserve" /* Specify what JSX code is generated. */, // "libReplacement": true, /* Enable lib replacement. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ @@ -26,7 +26,7 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ + "module": "commonjs" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ @@ -80,12 +80,12 @@ // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ @@ -108,6 +108,6 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..a60779c --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + esbuild: { + jsxFactory: "createElement", + }, +});