Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ import {TODO_ITEMS} from './constants/todoData';
import TodoHeader from './components/TodoHeader';
import TodoList from './components/TodoList';
import EmptyState from './components/EmptyState';
import TodoInput from './components/TodoInput';

export default function App() {
const [todoItems, setTodoItems] = useState(TODO_ITEMS);

const addTodo = (task: string) => {
const newTodo = {
id: `todo-${Date.now()}`,
task,
isCompleted: false,
};
setTodoItems((prev) => [...prev, newTodo]);
};

const toggleTodo = (id: string) => {
setTodoItems(prevItems =>
Expand All @@ -15,6 +25,10 @@ export default function App() {
);
};

const deleteTodo = (id: string) => {
setTodoItems((prev) => prev.filter((item) => item.id !== id));
};

const appBackgroundStyle: React.CSSProperties = {
backgroundColor: 'var(--bg)',
minHeight: '100vh',
Expand All @@ -40,8 +54,9 @@ export default function App() {
<div style={appBackgroundStyle}>
<main style={todoContainerStyle}>
<TodoHeader title="✅ 오늘의 할 일" />
<TodoInput onAdd={addTodo} />
{todoItems.length > 0 ? (
<TodoList items={todoItems} onToggle={toggleTodo} />
<TodoList items={todoItems} onToggle={toggleTodo} onDelete={deleteTodo} />
) : (
<EmptyState />
)}
Expand Down
54 changes: 45 additions & 9 deletions src/components/TodoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ interface TodoCardProps {
id: string;
task: string;
isCompleted: boolean;
onToggle: (id: string) => void;
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}

export default function TodoCard({ id, task, isCompleted, onToggle }: TodoCardProps) {
export default function TodoCard({ id, task, isCompleted, onToggle, onDelete }: TodoCardProps) {
const [isHovered, setIsHovered] = useState(false);

const cardStyle: React.CSSProperties = {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
gap: '12px',
backgroundColor: 'var(--bg-card)',
Expand All @@ -24,6 +26,13 @@ export default function TodoCard({ id, task, isCompleted, onToggle }: TodoCardPr
transition: 'box-shadow 0.2s ease',
};

const contentSectionStyle: React.CSSProperties = {
display: 'flex',
alignItems: 'center',
gap: '12px',
flex: 1,
};

const checkboxStyle: React.CSSProperties = {
width: '24px',
height: '24px',
Expand All @@ -37,6 +46,20 @@ export default function TodoCard({ id, task, isCompleted, onToggle }: TodoCardPr
transition: 'all 0.2s ease',
};


const deleteButtonStyle: React.CSSProperties = {
background: 'none',
border: 'none',
cursor: 'pointer',
padding: '4px',
fontSize: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
opacity: isHovered ? 1 : 0.5,
transition: 'opacity 0.2s',
};

const textStyle: React.CSSProperties = {
font: 'var(--font-body)',
color: isCompleted ? 'var(--text-secondary)' : 'var(--text)',
Expand All @@ -51,14 +74,27 @@ export default function TodoCard({ id, task, isCompleted, onToggle }: TodoCardPr
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div style={checkboxStyle}>
{isCompleted && (
<svg width="12" height="10" viewBox="0 0 12 10" fill="none">
<path d="M1 5L4.5 8.5L11 1.5" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)}
<div style={contentSectionStyle}>
<div style={checkboxStyle}>
{isCompleted && (
<svg width="12" height="10" viewBox="0 0 12 10" fill="none">
<path d="M1 5L4.5 8.5L11 1.5" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)}
</div>
<span style={textStyle}>{task}</span>
</div>
<span style={textStyle}>{task}</span>

<button
style={deleteButtonStyle}
onClick={(e) => {
e.stopPropagation();
onDelete(id);
}}
aria-label="삭제"
>
🗑
</button>
</div>
);
}
71 changes: 71 additions & 0 deletions src/components/TodoInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useState } from 'react';

interface TodoInputProps {
onAdd: (task: string) => void;
}

export default function TodoInput({ onAdd }: TodoInputProps) {
const [text, setText] = useState('');
const [isFocused, setIsFocused] = useState(false);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!text.trim()) return;
onAdd(text);
setText('');
};

const containerStyle: React.CSSProperties = {
display: 'flex',
gap: '8px',
width: '100%',
marginTop: '32.4px',
};

const inputBaseStyle: React.CSSProperties = {
flex: 1,
padding: '12px 16px',
borderRadius: '12px',
border: '1px solid var(--border)',
backgroundColor: 'var(--bg-card)',
font: 'var(--font-body)',
outline: 'none',
boxSizing: 'border-box',
};

const inputFocusStyle: React.CSSProperties = {
borderRadius: '8px',
border: '2px solid #3B82F6',
background: 'rgba(255, 255, 255, 0.00)',
};

const buttonStyle: React.CSSProperties = {
padding: '12px 24px',
borderRadius: '12px',
border: 'none',
backgroundColor: '#3B82F6',
color: 'white',
font: 'var(--font-sub)',
cursor: 'pointer',
};

return (
<form style={containerStyle} onSubmit={handleSubmit}>
<input
type="text"
style={{
...inputBaseStyle,
...(isFocused ? inputFocusStyle : {}),
}}
placeholder="할 일을 입력하세요"
value={text}
onChange={(e) => setText(e.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
<button type="submit" style={buttonStyle}>
추가
</button>
</form>
);
}
4 changes: 3 additions & 1 deletion src/components/TodoList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ interface TodoItem {
interface TodoListProps {
items: TodoItem[];
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}


export default function TodoList({ items, onToggle }: TodoListProps) {
export default function TodoList({ items, onToggle, onDelete }: TodoListProps) {
const listStyle: React.CSSProperties = {
listStyle: 'none',
padding: 0,
Expand All @@ -31,6 +32,7 @@ export default function TodoList({ items, onToggle }: TodoListProps) {
task={item.task}
isCompleted={item.isCompleted}
onToggle={onToggle}
onDelete={onDelete}
/>
</li>
))}
Expand Down
3 changes: 2 additions & 1 deletion src/constants/todoData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export const TODO_ITEMS = [
{ id: 'todo-1', task: "리액트 공식문서 읽기", isCompleted: true },
{ id: 'todo-2', task: "알고리즘 문제 풀기", isCompleted: true },
{ id: 'todo-3', task: "운동 30분 하기", isCompleted: false },
{ id: 'todo-4', task: "프로젝트 회의 준비", isCompleted: false }
{ id: 'todo-4', task: "프로젝트 회의 준비", isCompleted: false },
{ id: 'todo-5', task: "장보기 하기", isCompleted: false }
];