React/MouseMoveEvent
React Project - TodoList Description WebAPIs를 기반으로 마우스에 따라 이동하는 원 구...
import './App.css';
import TodoList from './components/TodoList/TodoList';
function App() {
return <div>
<TodoList />
</div>
}
import React, {useState} from 'react';
export default function TodoList() {
const [todos, setTodos] = useState([
{id: '123', text: '장보기', status: 'active'},
{id: '124', text: '공부하기', status: 'active'}
])
return <section>
<ul>
{
todos.map((item) => (
<li key={item.id}>{item.text}</li>
))
}
</ul>
</section>
}
<ul> <ol> <li> <h1> <p>
등의 태그를 포함한다. 일반적인 구조는 아래와 같다 <section>
<h1>상품소개</h1>
<ul>
<li>상품1<li>
<li>상품2<li>
<li>상품3<li>
</ul>
<p>상품이 실제 이미지와 다를 수 있습니다</p>
</section>
import React, {useState} from 'react';
import AddTodo from '../AddTodo/AddTodo';
export default function TodoList() {
const [todos, setTodos] = useState([
{id: '123', text: '장보기', status: 'active'},
{id: '124', text: '공부하기', status: 'active'}
])
const handleAdd = (todo) => {
setTodos([...todos, todo])
}
return <section>
<ul>
{
todos.map((item) => (
<li key={item.id}>{item.text}</li>
))
<AddTodo onAdd={handleAdd}/>
}
</ul>
</section>
}
import React, {useState} from 'react';
import {v4 as uuid4} from 'uuid';
export default function AddTodo({ onAdd }) {
const [text, setText] = useState();
const handleChange = (e) => setText(e.target.value)
const handleSubmit = (e) => {
e.preventDefault();
if(text.trim().length === 0) {
return;
}
onAdd({id: '고유한값', text: text, status: 'active'});
setText('');
}
return (
<form onSubmit={handleSubmit}>
<input
type='text'
placeholder='AddTodo'
value='text'
onChange={handleChange}
/>
<button>Add<button>
</form>
)
}
import React, {useState} from 'react';
import AddTodo from '../AddTodo/AddTodo';
import Todo from '../Todo/Todo';
export default function TodoList() {
const [todos, setTodos] = useState([
{id: '123', text: '장보기', status: 'active'},
{id: '124', text: '공부하기', status: 'active'}
])
const handleAdd = (todo) => {setTodos([...todos, todo])}
const handleUpdate = (updated) =>
setTodos(todos.map((t) => (t.id === updated.id ? updated : t)));
const handleDelete = (deleted) =>
setTodos(todos.filter((t) => t.id !== deleted.id));
return <section>
<ul>
{
todos.map((item) => (
<Todo
key={item.id}
todo={item}
onUpdate={handleUpdate}
onDelete={handleDelete}/>
))}
<AddTodo onAdd={handleAdd}/>
</ul>
</section>
}
import React, {useState} from 'react';
import {FaTrashAlt} from 'react-icons/fa'
export default function Todo({todo, onUpdate, onDelete}) {
const {text} = todo;
const handleChange = (e) => {
const status = e.target.checked ? 'completed' : 'active';
onUpdate({...todo, status:status});
}
const handleDelete = () => onDelete(todo)
return (
<li>
<input
type='checkbox'
id='checkbox'
onChange={handleChange}
/>
<label htmlFor='checkbox'>{text}</label>
<button onClick={handleDelete}>
<FaTrashAlt/>
</button>
</li>
)
}
<li>
를 통해 Todos의 목록을 출력하였으나, 업데이트와 삭제기능 구현을 위해 코드수정 필요 - Todo컴포넌트를 통해 업데이트와 삭제기능 구현 <label>
: form요소에 이름표를 붙이기 위한 것으로 나이를 적는 폼 앞에 ‘나이’와 같은 이름표를 붙여주는 것 / 폼 요소를 위한 레이블을 생성하는 경우 id, htmlFor를 사용해 연결되어 있음을 나타냄)
import './App.css';
import Header from './components/Header/Header';
import TodoList from './components/TodoList/TodoList'
import React, {useState} from 'react';
const filters = ['all', 'active', 'complited'];
function App() {
const [filter, setFilter] = useState(filters[0]);
return(
<div>
<Header
filter={filter}
filters={filters}
onFilterChange={setFilter}
/>
<TodoList filter={filter} />
</div>
)
}
export default App;
import React from 'react'
export default function Header({ filters, onFilterChange }) {
return (
<header>
<ul>
{filters.map((value, index) => <li key={index}>
<button onClick={() => onFilterChange(value)}>{value}</button>
</li>
)}
</ul>
</header>);
}
import React, {useState} from 'react';
import AddTodo from '../AddTodo/AddTodo';
import Todo from '../Todo/Todo';
export default function TodoList({ filter }) {
const [todos, setTodos] = useState([
{id: '123', text: '장보기', status: 'active'},
{id: '124', text: '공부하기', status: 'active'}
])
const handleAdd = (todo) => {setTodos([...todos, todo])}
const handleUpdate = (updated) =>
setTodos(todos.map((t) => (t.id === updated.id ? updated : t)));
const handleDelete = (deleted) =>
setTodos(todos.filter((t) => t.id !== deleted.id));
const filtered = getFilteredItems(todos, filter);
return <section>
<ul>
{
filtered.map((item) => (
<Todo
key={item.id}
todo={item}
onUpdate={handleUpdate}
onDelete={handleDelete}/>
))}
<AddTodo onAdd={handleAdd}/>
</ul>
</section>
}
function getFilteredItems(todos, filter) {
if(filter === 'all') {
return todos;
}
return todos.filter(todo => todo.status === filter);
}
<ul><li>
태그를 사용해 filters의 값을 버튼으로 보여줌 - 개별 리스트의 키값은 고정된 배열이니 index로 설정 - button의 텍스트는 filters의 value로 설정 - 클릭이 발생할 경우, onFilterChange를 호출해 value전달
:root {
--color-bg-dark: #f5f5f5;
--color-bg: #fdfffd;
--color-grey: #d1d1d1;
--color-text: #22243b;
--color-accent: #f16e03;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: rgb(81, 87, 111);
background: linear-gradient(
90deg,
rgba(8, 87, 111, 1) 0%,
rgba(60, 61, 69, 1) 100%,);
}
#root {
width: 100%;
height: 60%;
max-width: 500px;
background-color: var(--color-bg-dark);
border-radius: 1rem;
display: flex;
padding-left : 10px;
flex-direction: column;
-webkit-box-shadow: 17px 10px 23px 0px rgba(0,0,0,0.45);
-moz-box-shadow: 17px 10px 23px 0px rgba(0,0,0,0.45);
box-shadow: 17px 10px 23px 0px rgba(0,0,0,0.45);
overflow: hidden;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
ul {
list-style: none;
padding-left: 0;
}
:root {
--color-bg-dark: #f5f5f5;
}
#root {
background-color: var(--color-bg-dark);
}
display: flex; //아이템이 수평기준 중간에 오도록 설정
justify-content: center;
align-items: center; //아이템이 수직기준 중간에 오도록 설정
/*
display: flex;
justify-content: center;
-> 아이템이 수평기준 중간에 오도록 설정
-> display: flex는 CSS3부터 지원되며 요소들을 자유자제로 위치시키는 속성이다
-> justify-content는 가로축을 기준으로 좌우에 대한 정렬을 관장한다
align-items: center;
-> 아이템이 수직기준 중간에 오도록 설정
-> align-items은 세로축을 기준으로 상하에 대한 정렬을 관장한다
*/
import React from 'react'
import styles from './Header.module.css';
export default function Header({ filters, onFilterChange }) {
return (
<header className={styles.header}>
<ul className={styles.filters}>
{filters.map((value, index) => <li key={index} >
<button className={styles.filter} onClick={() => onFilterChange(value)}>{value}</button>
</li>
)}
</ul>
</header>);
}
.header{
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--color-bg-dark);
border-bottom: 1px solid var(--color-grey);
}
.filters {
display: flex;
}
.filter {
font-size: 1.4rem;
margin: 0.3rem;
text-transform: capitalize;
background-color: transparent;
color: var(--color-accent);
opacity: 0.6;
font-weigh: bold;
}
.filter:hover {
opacity: 1;
}
.filter.selected {
opacity: 1;
}
.filter.sected::after {
content: '';
display: block;
margin-top: 0.2rem;
border: 1px solid var(--color-text);
}
Avenco comes with a built-in contact form.