|
@@ -0,0 +1,166 @@
|
|
|
|
|
+import { useState, useRef } from 'react'
|
|
|
|
|
+import './App.css'
|
|
|
|
|
+
|
|
|
|
|
+interface Todo {
|
|
|
|
|
+ id: number,
|
|
|
|
|
+ text: string,
|
|
|
|
|
+ completed: boolean
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type Filter = 'all' | 'active' | 'completed';
|
|
|
|
|
+
|
|
|
|
|
+function filterTodos(todos: Todo[], filter: Filter): Todo[] {
|
|
|
|
|
+ switch (filter) {
|
|
|
|
|
+ case 'all':
|
|
|
|
|
+ return todos;
|
|
|
|
|
+ case 'active':
|
|
|
|
|
+ return todos.filter(todo => !todo.completed);
|
|
|
|
|
+ case 'completed':
|
|
|
|
|
+ return todos.filter(todo => todo.completed);
|
|
|
|
|
+ default:
|
|
|
|
|
+ return todos;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+function Header({ addTodo }: { addTodo: (text: string) => void }) {
|
|
|
|
|
+ const inpRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
|
|
|
+ if (e.keyCode === 13) {
|
|
|
|
|
+ addTodo(inpRef.current!.value);
|
|
|
|
|
+ inpRef.current!.value = ""
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <header className="header">
|
|
|
|
|
+ <h1>todos</h1>
|
|
|
|
|
+ <input
|
|
|
|
|
+ ref={inpRef}
|
|
|
|
|
+ autoFocus
|
|
|
|
|
+ autoComplete="off"
|
|
|
|
|
+ placeholder="输入您要完成的任务?"
|
|
|
|
|
+ className="new-todo"
|
|
|
|
|
+ onKeyDown={handleKeyDown}
|
|
|
|
|
+ />
|
|
|
|
|
+ </header>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function Main({ onToggleAll, allCompleted }: { onToggleAll: (val: boolean) => void, allCompleted: boolean }) {
|
|
|
|
|
+ const handleToggle = () => {
|
|
|
|
|
+ allCompleted = !allCompleted;
|
|
|
|
|
+ onToggleAll(allCompleted)
|
|
|
|
|
+ }
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <input id="toggle-all" type="checkbox" className="toggle-all" onChange={handleToggle} checked={allCompleted} />
|
|
|
|
|
+ <label htmlFor="toggle-all"></label>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function TodoItem({ todo, onDelete, onActive }: { todo: Todo, onDelete: (id: number) => void, onActive: (id: number) => void }) {
|
|
|
|
|
+
|
|
|
|
|
+ const deleteTodo = (val: number) => {
|
|
|
|
|
+ onDelete(val)
|
|
|
|
|
+ }
|
|
|
|
|
+ const activeTodo = (val: number) => {
|
|
|
|
|
+ onActive(val)
|
|
|
|
|
+ }
|
|
|
|
|
+ return (
|
|
|
|
|
+ <li className={todo.completed ? 'completed' : ''}>
|
|
|
|
|
+ <div className="view">
|
|
|
|
|
+ <input type="checkbox" className="toggle" checked={todo.completed} onChange={() => activeTodo(todo.id)} />
|
|
|
|
|
+ <label>{todo.text}</label>
|
|
|
|
|
+ <button className="destroy" onClick={() => deleteTodo(todo.id)}></button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <input type="text" className="edit" />
|
|
|
|
|
+ </li>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function Footer({ count, clearAll, filter, tabFilter }: { count: number, clearAll: () => void, filter: Filter, tabFilter: (val: string) => void }) {
|
|
|
|
|
+ console.log(filter, 'filter')
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <span className="todo-count"><strong>{count}</strong> items left </span>
|
|
|
|
|
+ <ul className="filters">
|
|
|
|
|
+ <li><a href="#/all" onClick={() => tabFilter('all')} className={filter == 'all' ? 'selected' : ''}>All</a></li>
|
|
|
|
|
+ <li><a href="#/active" onClick={() => tabFilter('active')} className={filter === 'active' ? 'selected' : ''}>Active</a></li>
|
|
|
|
|
+ <li><a href="#/completed" onClick={() => tabFilter('completed')} className={filter === 'completed' ? 'selected' : ''}>Completed</a></li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <button className="clear-completed" onClick={clearAll}>Clear completed</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function App() {
|
|
|
|
|
+ const [todos, setTodos] = useState<Todo[]>([
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 1, text: '吃饭', completed: false
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 2, text: '睡觉', completed: false
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 3, text: '打豆豆', completed: true
|
|
|
|
|
+ }
|
|
|
|
|
+ ])
|
|
|
|
|
+ const completeCount = todos.filter((item) => !item.completed).length;
|
|
|
|
|
+ const isCompleted = todos.every((item) => item.completed);
|
|
|
|
|
+ const [isFilter, setIsFilter] = useState<Filter>('all');
|
|
|
|
|
+ const newArr = filterTodos(todos, isFilter);
|
|
|
|
|
+ const handleAdd = (val: string) => {
|
|
|
|
|
+ setTodos([...todos, {
|
|
|
|
|
+ id: Date.now(),
|
|
|
|
|
+ text: val,
|
|
|
|
|
+ completed: false
|
|
|
|
|
+ }])
|
|
|
|
|
+ }
|
|
|
|
|
+ const handleDelete = (val: number) => {
|
|
|
|
|
+ setTodos((prevTodos) => { return prevTodos.filter((todo) => todo.id !== val) })
|
|
|
|
|
+ }
|
|
|
|
|
+ const handleAvtive = (val: number) => {
|
|
|
|
|
+ setTodos((prevTodos) => prevTodos.map((todo) => {
|
|
|
|
|
+ return todo.id === val ? { ...todo, completed: !todo.completed } : todo
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ const handleAll = (val: boolean) => {
|
|
|
|
|
+ setTodos((prev) => prev.map((todo) => {
|
|
|
|
|
+ return { ...todo, completed: val }
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ const handleClear = () => {
|
|
|
|
|
+ setTodos([...todos.filter((todo) => !todo.completed)])
|
|
|
|
|
+ }
|
|
|
|
|
+ const handleSet = (val: Filter) => {
|
|
|
|
|
+ setIsFilter(val)
|
|
|
|
|
+ }
|
|
|
|
|
+ return (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <div id="app">
|
|
|
|
|
+ <section className="todoapp">
|
|
|
|
|
+ <Header addTodo={handleAdd}></Header>
|
|
|
|
|
+ <section className="main">
|
|
|
|
|
+ <Main onToggleAll={handleAll} allCompleted={isCompleted}></Main>
|
|
|
|
|
+ <ul className="todo-list">
|
|
|
|
|
+ {
|
|
|
|
|
+ newArr.map((todo) => {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <TodoItem key={todo.id} todo={todo} onDelete={handleDelete} onActive={handleAvtive}></TodoItem>
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ <footer className="footer">
|
|
|
|
|
+ <Footer count={completeCount} clearAll={handleClear} filter={isFilter} tabFilter={handleSet}></Footer>
|
|
|
|
|
+ </footer>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ </>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default App
|