-
Notifications
You must be signed in to change notification settings - Fork 8
[2주차] 심여은 미션 제출합니다. #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
17493f3
2fea1d9
c3a1e05
eba1249
f0348be
d7e4361
6340f8a
c470253
52fed0c
91df3a8
2b57d8f
a44e73b
d0b6547
176193a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,130 @@ | ||||||||||||||||||
| import { useEffect, useState } from 'react'; | ||||||||||||||||||
| import { Global, css } from '@emotion/react'; | ||||||||||||||||||
| import styled from '@emotion/styled'; | ||||||||||||||||||
| import InputForm from './components/InputForm.jsx'; | ||||||||||||||||||
| import TodoList from './components/TodoList.jsx'; | ||||||||||||||||||
| import Header from './components/Header.jsx'; | ||||||||||||||||||
| import bgImage from './grid.jpg'; | ||||||||||||||||||
|
|
||||||||||||||||||
| const GlobalStyles = () => ( | ||||||||||||||||||
| <Global | ||||||||||||||||||
| styles={css` | ||||||||||||||||||
| @font-face { | ||||||||||||||||||
| font-family: 'RixXladywatermelonR'; | ||||||||||||||||||
| src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/2408-4@1.0/RixXladywatermelonR.woff2') | ||||||||||||||||||
| format('woff2'); | ||||||||||||||||||
| font-weight: normal; | ||||||||||||||||||
| font-style: normal; | ||||||||||||||||||
| } | ||||||||||||||||||
| html { | ||||||||||||||||||
| margin: 0; | ||||||||||||||||||
| display: flex; | ||||||||||||||||||
| justify-content: center; | ||||||||||||||||||
| align-items: center; | ||||||||||||||||||
| height: 100vh; | ||||||||||||||||||
| } | ||||||||||||||||||
| body { | ||||||||||||||||||
| font-family: 'RixXladywatermelonR', sans-serif; | ||||||||||||||||||
| max-width: 500px; | ||||||||||||||||||
| padding: 10px; | ||||||||||||||||||
| text-align: center; | ||||||||||||||||||
| display: flex; | ||||||||||||||||||
| justify-content: center; | ||||||||||||||||||
| align-items: center; | ||||||||||||||||||
| scrollbar-width: thin; | ||||||||||||||||||
| margin: 0; | ||||||||||||||||||
| } | ||||||||||||||||||
| button { | ||||||||||||||||||
| font-family: 'RixXladywatermelonR'; | ||||||||||||||||||
| display: flex; | ||||||||||||||||||
| justify-content: center; | ||||||||||||||||||
| align-content: center; | ||||||||||||||||||
| flex-wrap: wrap-reverse; | ||||||||||||||||||
| } | ||||||||||||||||||
| `} | ||||||||||||||||||
| /> | ||||||||||||||||||
| ); | ||||||||||||||||||
|
Comment on lines
+9
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Global style을 통해서 하나의 디자인 컴포넌트에 한번에 css를 정리할 수 있군요!🤩
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 주현님에게 전역 스타일에 대해 리뷰를 들었는데 이렇게 사용하는 거군요! 확실히 훨씬 깔끔한 것 같아요 다음 미션 때 참고해서 적용해보겠습니다😆 |
||||||||||||||||||
|
|
||||||||||||||||||
| const ContainerStyle = styled.div` | ||||||||||||||||||
| position: relative; | ||||||||||||||||||
| display: flex; | ||||||||||||||||||
| width: 450px; | ||||||||||||||||||
| height: 680px; | ||||||||||||||||||
| flex-direction: column; | ||||||||||||||||||
| background-color: #fef79fbd; | ||||||||||||||||||
| overflow: hidden; | ||||||||||||||||||
| ::after { | ||||||||||||||||||
| content: ''; | ||||||||||||||||||
| position: absolute; | ||||||||||||||||||
| top: 0; | ||||||||||||||||||
| left: 0; | ||||||||||||||||||
| width: 100%; | ||||||||||||||||||
| height: 100%; | ||||||||||||||||||
| background-image: url(${bgImage}); | ||||||||||||||||||
| background-size: cover; | ||||||||||||||||||
| background-blend-mode: darken; | ||||||||||||||||||
| opacity: 0.5; | ||||||||||||||||||
| z-index: -1; | ||||||||||||||||||
| } | ||||||||||||||||||
| `; | ||||||||||||||||||
|
|
||||||||||||||||||
| function App() { | ||||||||||||||||||
| return <div>React Todo</div>; | ||||||||||||||||||
| const userName = '옹헤'; | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
74
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 상수화 하여 사용하신 점 아주 좋습니다!!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 또한 컴포넌트는 함수이기 때문에 상태가 업데이트 될 때마다 컴포넌트 내부에 선언된 변수는 매번 메모리에 재할당 됩니다!
Suggested change
|
||||||||||||||||||
| //localStorage에서 todoList 불러오기 | ||||||||||||||||||
| const [todoList, setTodoList] = useState(() => { | ||||||||||||||||||
| const prevTodoList = localStorage.getItem('todoList'); | ||||||||||||||||||
| return prevTodoList ? JSON.parse(prevTodoList) : []; | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| //todoList가 변경될 때마다 localStorage에 저장 | ||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||
| localStorage.setItem('todoList', JSON.stringify(todoList)); | ||||||||||||||||||
| }, [todoList]); | ||||||||||||||||||
|
Comment on lines
+81
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. useEffect 사용해서 재랜더링 조건 부여하신거 좋은 것 같습니다! |
||||||||||||||||||
|
|
||||||||||||||||||
| //사용자에게 받은 입력으로 todo 추가하기 | ||||||||||||||||||
| function handleAddTodo(todoInput) { | ||||||||||||||||||
| const newTodo = { | ||||||||||||||||||
| createTime: Date.now(), | ||||||||||||||||||
|
Comment on lines
+89
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙋질문 있습니다~!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 같은 이유로 궁금합니다..! |
||||||||||||||||||
| content: todoInput, | ||||||||||||||||||
| isCompleted: false, | ||||||||||||||||||
| }; | ||||||||||||||||||
| setTodoList([...todoList, newTodo]); | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스프레드 연산자를 활용해서 추가하는 방법이 훨 간단하고 보기에도 편하네요👍
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
그 이유가 궁금하다면... 프로젝트를 진행하다 보면
반면 |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function handleComplete(todo) { | ||||||||||||||||||
| const newTodoList = todoList.map((item) => { | ||||||||||||||||||
| if (item.createTime === todo.createTime) { | ||||||||||||||||||
| return { ...item, isCompleted: !item.isCompleted }; | ||||||||||||||||||
| } | ||||||||||||||||||
| return item; | ||||||||||||||||||
| }); | ||||||||||||||||||
| setTodoList(newTodoList); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function handleDelete(todo) { | ||||||||||||||||||
| const newTodoList = todoList.filter((item) => { | ||||||||||||||||||
| return item.createTime !== todo.createTime; | ||||||||||||||||||
| }); | ||||||||||||||||||
|
Comment on lines
+108
to
+110
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수가 직접 표현식을 반환하는 경우에는 중괄호와
Suggested change
|
||||||||||||||||||
| setTodoList(newTodoList); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <> | ||||||||||||||||||
| <GlobalStyles /> | ||||||||||||||||||
| <ContainerStyle> | ||||||||||||||||||
| <Header name={userName} /> {/* Header 컴포넌트 */} | ||||||||||||||||||
| <InputForm handleAddTodo={handleAddTodo} /> {/* InputForm 컴포넌트 */} | ||||||||||||||||||
| <TodoList | ||||||||||||||||||
| todoList={todoList} | ||||||||||||||||||
| handleComplete={handleComplete} | ||||||||||||||||||
| handleDelete={handleDelete} | ||||||||||||||||||
| /> {/* TodoList 컴포넌트 */} | ||||||||||||||||||
| </ContainerStyle> | ||||||||||||||||||
| </> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export default App; | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import styled from "@emotion/styled"; | ||
|
|
||
| const HeaderStyle = styled.header` | ||
| font-size: 40px; | ||
| font-weight: bold; | ||
| padding: 20px; | ||
| background-color: #2b3681; | ||
| color: white; | ||
| box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.5); | ||
| `; | ||
|
|
||
| export default function Header({ name }) { | ||
| return <HeaderStyle>{name}'s React Todo</HeaderStyle>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import styled from '@emotion/styled'; | ||
| import { useState } from 'react'; | ||
|
|
||
| const InputFormStyle = styled.form` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| margin-top: 20px; | ||
| `; | ||
|
|
||
| const InputLabelStyle = styled.label` | ||
| align-items: center; | ||
| padding: 2px 7px; | ||
| font-size: 20px; | ||
| `; | ||
|
|
||
| const InputStyle = styled.input` | ||
| border-color: black; | ||
| border-width: 0 0 2px; | ||
| background-color: transparent; | ||
| width: 180px; | ||
| margin: 0; | ||
| font-family: RixXladywatermelonR; | ||
| padding-bottom: 5px; | ||
| color: rgb(50, 50, 50); | ||
| :focus { | ||
| outline: none; | ||
| box-shadow: none; | ||
| } | ||
| `; | ||
|
|
||
| const InputBtnStyle = styled.button` | ||
| border: transparent; | ||
| border-radius: 4px; | ||
| background-color: transparent; | ||
| padding: 2px 7px; | ||
| font-size: 25px; | ||
| font-weight: 500; | ||
| :hover { | ||
| color: green; | ||
| background-color: transparent; | ||
| } | ||
| `; | ||
|
|
||
| export default function InputForm({ handleAddTodo }) { | ||
| const [todoInput, setTodoInput] = useState(''); | ||
|
|
||
| const handleInputChange = (e) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네이밍 아주 좋습니다 👍🏻 |
||
| setTodoInput(e.target.value); | ||
| }; | ||
|
|
||
| const handleInputSubmit = (e) => { | ||
| e.preventDefault(); | ||
| //입력값이 없으면 alert 띄우기 | ||
| if (todoInput === '') { | ||
| alert('한 글자 이상 입력해주세요.'); | ||
| return; | ||
| } | ||
|
Comment on lines
+54
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 빈 문자열 방지하는 alert 좋은 것같아요~!👍 |
||
| handleAddTodo(todoInput); | ||
| setTodoInput(''); // input 초기화 | ||
| }; | ||
|
|
||
| return ( | ||
| <> | ||
| <InputFormStyle onSubmit={handleInputSubmit}> | ||
| <InputLabelStyle htmlFor="todo-input">할 일 : </InputLabelStyle> | ||
| <InputStyle | ||
| id="todo-input" | ||
| placeholder="할 일을 입력하세요" | ||
| value={todoInput} | ||
| onChange={handleInputChange} | ||
| /> | ||
| <InputBtnStyle id="input-btn" type="submit"> | ||
| + | ||
| </InputBtnStyle> | ||
| </InputFormStyle> | ||
| </> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,58 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| import styled from '@emotion/styled'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const ListLiStyle = styled.li` | ||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||||||||
| padding: 8px 30px; | ||||||||||||||||||||||||||||||||||||||||||||
| gap: 8px; | ||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const DoBtnStyle = styled.button` | ||||||||||||||||||||||||||||||||||||||||||||
| width: 25px; | ||||||||||||||||||||||||||||||||||||||||||||
| height: 25px; | ||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 8px; | ||||||||||||||||||||||||||||||||||||||||||||
| background-color: transparent; | ||||||||||||||||||||||||||||||||||||||||||||
| font-size: 25px; | ||||||||||||||||||||||||||||||||||||||||||||
| border-color: #393939; | ||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const DoneBtnStyle = styled.button` | ||||||||||||||||||||||||||||||||||||||||||||
| width: 25px; | ||||||||||||||||||||||||||||||||||||||||||||
| height: 25px; | ||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 8px; | ||||||||||||||||||||||||||||||||||||||||||||
| border: transparent; | ||||||||||||||||||||||||||||||||||||||||||||
| background-color: green; | ||||||||||||||||||||||||||||||||||||||||||||
| font-size: 20px; | ||||||||||||||||||||||||||||||||||||||||||||
| font-weight: 500; | ||||||||||||||||||||||||||||||||||||||||||||
| color: white; | ||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const DeleteBtnStyle = styled.button` | ||||||||||||||||||||||||||||||||||||||||||||
| border: transparent; | ||||||||||||||||||||||||||||||||||||||||||||
| background-color: transparent; | ||||||||||||||||||||||||||||||||||||||||||||
| font-size: 20px; | ||||||||||||||||||||||||||||||||||||||||||||
| font-weight: 500; | ||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const DoneSpanStyle = styled.span` | ||||||||||||||||||||||||||||||||||||||||||||
| text-decoration: line-through; | ||||||||||||||||||||||||||||||||||||||||||||
| color: #b3b3b3; | ||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| export default function TodoItem({ todo, handleComplete, handleDelete }) { | ||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||
| <ListLiStyle> | ||||||||||||||||||||||||||||||||||||||||||||
| {todo.isCompleted ? ( | ||||||||||||||||||||||||||||||||||||||||||||
| <DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle> | ||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||
| <DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
| {todo.isCompleted ? ( | ||||||||||||||||||||||||||||||||||||||||||||
| <DoneSpanStyle>{todo.content}</DoneSpanStyle> | ||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||
| <span>{todo.content}</span> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
| <DeleteBtnStyle onClick={() => handleDelete(todo)}>x</DeleteBtnStyle> | ||||||||||||||||||||||||||||||||||||||||||||
| </ListLiStyle> | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+56
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 삼항연산자로 조건부 렌더링하니까 정말 간편하네요.. 이야 깔끔하다~ ! |
||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import styled from '@emotion/styled'; | ||
| import TodoItem from './TodoItem'; | ||
|
|
||
| const SectionStyle = styled.section` | ||
| margin-top: 20px; | ||
| `; | ||
|
|
||
| const SectionTitleStyle = styled.h4` | ||
| font-size: 20px; | ||
| text-align: left; | ||
| padding: 10px 20px; | ||
| margin: 0; | ||
| `; | ||
|
|
||
| const ListUlStyle = styled.ul` | ||
| list-style: none; | ||
| padding: 0; | ||
| margin: 0; | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: flex-start; | ||
| height: 168px; | ||
| overflow-y: scroll; | ||
| `; | ||
|
|
||
| export default function TodoList({ todoList, handleComplete, handleDelete }) { | ||
| // todoList에서 완료되지 않은 todoList와 완료된 todoList를 분리 | ||
| const doList = todoList.filter((todo) => !todo.isCompleted); | ||
| const doneList = todoList.filter((todo) => todo.isCompleted); | ||
|
|
||
|
Comment on lines
+26
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. filter 사용해서 doList와 doneList에 나눠서 처리해주시는거 좋은 것같아요!🤩 |
||
| return ( | ||
| <> | ||
| <SectionStyle> | ||
| <SectionTitleStyle>📋 Todo ({doList.length})</SectionTitleStyle> | ||
| <ListUlStyle> | ||
| {doList.map((todo) => | ||
| <TodoItem | ||
| key={todo.createTime} | ||
| todo={todo} | ||
| handleComplete={handleComplete} | ||
| handleDelete={handleDelete} | ||
| /> | ||
| )} | ||
| </ListUlStyle> | ||
| </SectionStyle> | ||
| <SectionStyle> | ||
|
Comment on lines
+34
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요 부분을 컴포넌트로 한 번 더 분리해도 좋을 것 같아요! |
||
| <SectionTitleStyle>👍🏻 Done ({doneList.length})</SectionTitleStyle> | ||
| <ListUlStyle> | ||
| {doneList.map((todo) => | ||
| <TodoItem | ||
| key={todo.createTime} | ||
| todo={todo} | ||
| handleComplete={handleComplete} | ||
| handleDelete={handleDelete} | ||
| /> | ||
| )} | ||
| </ListUlStyle> | ||
| </SectionStyle> | ||
| </> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오호
Global컴포넌트로 전역 스타일 설정하신 점 아주 좋습니다!!이 부분을 별도의 컴포넌트로 분리하고 import해서 사용하면 더욱 깔끔할 것 같아요 👍🏻