-
Notifications
You must be signed in to change notification settings - Fork 8
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
[2주차] 고윤정 미션 제출합니다. #2
base: main
Are you sure you want to change the base?
Changes from all commits
191725f
dcd69b6
1ffb7cd
a0feae0
bce271d
7adec98
2ebb7b2
773ed2f
7017327
0ef98ee
91f365f
c105875
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,85 @@ | ||||||||||
/** @jsxImportSource @emotion/react */ | ||||||||||
import { css } from '@emotion/react'; | ||||||||||
import 'normalize.css'; | ||||||||||
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. 전역적으로 스타일을 적용하고 싶다면 Emotion의 |
||||||||||
import { useEffect, useState } from 'react'; | ||||||||||
import InputBox from './components/InputBox.jsx'; | ||||||||||
import ToDoItemList from './components/ToDoItemList.jsx'; | ||||||||||
|
||||||||||
function App() { | ||||||||||
return <div>React Todo</div>; | ||||||||||
const bodyStyle = css` | ||||||||||
margin: 0; | ||||||||||
display: flex; | ||||||||||
justify-content: center; | ||||||||||
align-items: center; | ||||||||||
height: 100vh; | ||||||||||
background-image: linear-gradient( | ||||||||||
to bottom right, | ||||||||||
rgb(247, 196, 218), | ||||||||||
rgb(239, 239, 239) | ||||||||||
); | ||||||||||
`; | ||||||||||
Comment on lines
+9
to
+20
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. 스타일 코드는 컴포넌트( export default function App() {
// 생략
}
const wrapper = css`
// 생략
`; 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 = css` | ||||||||||
width: 350px; | ||||||||||
height: 600px; | ||||||||||
background-color: white; | ||||||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); | ||||||||||
border-radius: 20px; | ||||||||||
padding: 20px; | ||||||||||
`; | ||||||||||
|
||||||||||
const mainTitleStyle = css` | ||||||||||
@font-face { | ||||||||||
font-family: 'LINESeedKR-Bd'; | ||||||||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/LINESeedKR-Bd.woff2') | ||||||||||
format('woff2'); | ||||||||||
font-weight: 700; | ||||||||||
font-style: normal; | ||||||||||
} | ||||||||||
font-family: 'LINESeedKR-Bd'; | ||||||||||
color: rgb(59, 56, 56); | ||||||||||
`; | ||||||||||
|
||||||||||
// 초기 상태를 localStorage에서 불러옴 | ||||||||||
const [todoList, setTodoList] = useState(() => { | ||||||||||
const savedTodoList = localStorage.getItem('todoList'); | ||||||||||
return savedTodoList ? JSON.parse(savedTodoList) : []; | ||||||||||
}); | ||||||||||
Comment on lines
+43
to
+47
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. 👍🏻👍🏻 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. 오 엄청 간결하면서도 동작이 잘 보여요!! |
||||||||||
|
||||||||||
// todoList가 업데이트될 때마다 localStorage에 저장 | ||||||||||
useEffect(() => { | ||||||||||
localStorage.setItem('todoList', JSON.stringify(todoList)); | ||||||||||
}, [todoList]); | ||||||||||
|
||||||||||
return ( | ||||||||||
<div className="body" css={bodyStyle}> | ||||||||||
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. 클래스명을 부여하신 이유가 있으신가요?! |
||||||||||
<div className="container" css={containerStyle}> | ||||||||||
<h2 className="title" css={mainTitleStyle}> | ||||||||||
Comment on lines
+56
to
+57
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. emotion을 잘 적용해 주셔서 코드가 매우 간결하네요! |
||||||||||
To Do List | ||||||||||
</h2> | ||||||||||
{/* 할 일 입력 */} | ||||||||||
<InputBox todoList={todoList} setTodoList={setTodoList} /> | ||||||||||
|
||||||||||
{/* 할 일 목록 */} | ||||||||||
<ToDoItemList | ||||||||||
title={`📂 TO DO (${ | ||||||||||
todoList.filter((item) => !item.checked).length | ||||||||||
})`} // 아직 완료되지 않은목록의 길이 | ||||||||||
Comment on lines
+65
to
+67
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 todoCount = todoList.filter((item) => !item.checked).length;
Suggested change
|
||||||||||
todoList={todoList} | ||||||||||
setTodoList={setTodoList} | ||||||||||
checkedList={false} | ||||||||||
/> | ||||||||||
|
||||||||||
{/* 완료된 목록 */} | ||||||||||
<ToDoItemList | ||||||||||
title={`🗑️ DONE (${todoList.filter((item) => item.checked).length})`} // 완료된 목록의 길이 | ||||||||||
todoList={todoList} | ||||||||||
setTodoList={setTodoList} | ||||||||||
checkedList={true} | ||||||||||
/> | ||||||||||
</div> | ||||||||||
</div> | ||||||||||
); | ||||||||||
} | ||||||||||
|
||||||||||
export default App; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,111 @@ | ||||||||||||||||||||||||||||||
/** @jsxImportSource @emotion/react */ | ||||||||||||||||||||||||||||||
import { css } from '@emotion/react'; | ||||||||||||||||||||||||||||||
import 'normalize.css'; | ||||||||||||||||||||||||||||||
import { useRef, useState } from 'react'; | ||||||||||||||||||||||||||||||
import PropTypes from 'prop-types'; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
export default function InputBox({ todoList, setTodoList }) { | ||||||||||||||||||||||||||||||
const formStyle = css` | ||||||||||||||||||||||||||||||
display: flex; | ||||||||||||||||||||||||||||||
align-items: center; | ||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const inputStyle = css` | ||||||||||||||||||||||||||||||
width: 300px; | ||||||||||||||||||||||||||||||
height: 35px; | ||||||||||||||||||||||||||||||
outline: none; | ||||||||||||||||||||||||||||||
border-radius: 20px; | ||||||||||||||||||||||||||||||
border: 1.2px solid; | ||||||||||||||||||||||||||||||
border-color: rgb(247, 196, 218); | ||||||||||||||||||||||||||||||
padding-left: 12px; | ||||||||||||||||||||||||||||||
@font-face { | ||||||||||||||||||||||||||||||
font-family: 'LINESeedKR-Rg'; | ||||||||||||||||||||||||||||||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/LINESeedKR-Rg.woff2') | ||||||||||||||||||||||||||||||
format('woff2'); | ||||||||||||||||||||||||||||||
font-weight: 400; | ||||||||||||||||||||||||||||||
font-style: normal; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
Comment on lines
+21
to
+27
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.
|
||||||||||||||||||||||||||||||
font-family: 'LINESeedKR-Rg'; | ||||||||||||||||||||||||||||||
font-size: 12px; | ||||||||||||||||||||||||||||||
color: #3d3d3d; | ||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const addButtonStyle = css` | ||||||||||||||||||||||||||||||
border: 0; | ||||||||||||||||||||||||||||||
background-color: white; | ||||||||||||||||||||||||||||||
font-size: 20px; | ||||||||||||||||||||||||||||||
color: rgb(59, 56, 56); | ||||||||||||||||||||||||||||||
margin-left: 8px; | ||||||||||||||||||||||||||||||
cursor: pointer; | ||||||||||||||||||||||||||||||
&:hover { | ||||||||||||||||||||||||||||||
color: rgb(247, 196, 218); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const [text, setText] = useState(''); // input에 입력한 값 | ||||||||||||||||||||||||||||||
const inputRef = useRef(null); | ||||||||||||||||||||||||||||||
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. 저는 form을 사용하지 않아서 form을 사용했을 때 새로고침을 방지해야하는군요!! 배워갑니다!!! |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// form 제출 시 새로고침 방지 | ||||||||||||||||||||||||||||||
const formClickEvent = (e) => { | ||||||||||||||||||||||||||||||
e.preventDefault(); | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// input 값 가져오기 | ||||||||||||||||||||||||||||||
function onChangeInput(e) { | ||||||||||||||||||||||||||||||
setText(e.target.value); | ||||||||||||||||||||||||||||||
// e.target에 있는 <input.../>으로부터 value 값을 가져옴 | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
Comment on lines
+53
to
+57
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. 이벤트 핸들러의 이름은 보통 |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// + 버튼 클릭(form 제출) | ||||||||||||||||||||||||||||||
function onClickButton() { | ||||||||||||||||||||||||||||||
// 공백 입력 방지 | ||||||||||||||||||||||||||||||
if (text.trim() === '') return; | ||||||||||||||||||||||||||||||
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. 공백 입력을 방지한 부분 좋습니다👍🏻 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. 캬.. 공백 방지까지...디테일의 은혜가 끝이 없군요 |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// todoItemList에 값 추가 | ||||||||||||||||||||||||||||||
const AddTodoList = todoList.concat({ | ||||||||||||||||||||||||||||||
id: todoList.length, | ||||||||||||||||||||||||||||||
text, | ||||||||||||||||||||||||||||||
checked: false, | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
Comment on lines
+65
to
+69
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. TodoList 배열 안의 객체를 식별하는 id를 length로 주신 이유가 있을까요? 만약 id가 같다면 원하지 않는 다른 객체가 수정될 수 있을 것 같아요! |
||||||||||||||||||||||||||||||
setTodoList(AddTodoList); | ||||||||||||||||||||||||||||||
Comment on lines
+65
to
+70
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
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
setText(''); // input 값 초기화 | ||||||||||||||||||||||||||||||
inputRef.current.focus(); // 버튼 누른 후에도 input box에 자동 포커싱 | ||||||||||||||||||||||||||||||
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. useRef를 이용해서 input에 포커스를 주는 부분 새롭게 알아갑니다👀👀 |
||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||||||
<form onSubmit={formClickEvent} className="form" css={formStyle}> | ||||||||||||||||||||||||||||||
<input | ||||||||||||||||||||||||||||||
type="text" | ||||||||||||||||||||||||||||||
name="todoItem" | ||||||||||||||||||||||||||||||
value={text} | ||||||||||||||||||||||||||||||
ref={inputRef} | ||||||||||||||||||||||||||||||
className="input-box" | ||||||||||||||||||||||||||||||
placeholder="할일을 입력하세요" | ||||||||||||||||||||||||||||||
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. 이런 placeholder하나만으로도 이 사이트의 정체성을 보여주는 것 같아요. 🦖 친절한 사이트... |
||||||||||||||||||||||||||||||
onChange={onChangeInput} // input 값이 변하면(이벤트 발생) 메소드 실행 | ||||||||||||||||||||||||||||||
css={inputStyle} | ||||||||||||||||||||||||||||||
autoFocus | ||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||
<button | ||||||||||||||||||||||||||||||
className="add-button" | ||||||||||||||||||||||||||||||
onClick={onClickButton} | ||||||||||||||||||||||||||||||
css={addButtonStyle} | ||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||
+ | ||||||||||||||||||||||||||||||
</button> | ||||||||||||||||||||||||||||||
</form> | ||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// props 값 검증 | ||||||||||||||||||||||||||||||
InputBox.propTypes = { | ||||||||||||||||||||||||||||||
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. 나중에 타입스크립트도 잘하실 것 같아요 😉 |
||||||||||||||||||||||||||||||
todoList: PropTypes.arrayOf( | ||||||||||||||||||||||||||||||
PropTypes.shape({ | ||||||||||||||||||||||||||||||
id: PropTypes.number.isRequired, | ||||||||||||||||||||||||||||||
text: PropTypes.string.isRequired, | ||||||||||||||||||||||||||||||
}).isRequired | ||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||
setTodoList: PropTypes.func.isRequired, | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
Comment on lines
+104
to
+111
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. 오 props 값을 이렇게 검증할 수 있군요! 새로 알아갑니다👀 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
import { css } from '@emotion/react'; | ||
import 'normalize.css'; | ||
import PropTypes from 'prop-types'; | ||
|
||
export default function ToDoItem({ todoItem, todoList, setTodoList }) { | ||
const spanStyle = css` | ||
@font-face { | ||
font-family: 'LINESeedKR-Rg'; | ||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/LINESeedKR-Rg.woff2') | ||
format('woff2'); | ||
font-weight: 400; | ||
font-style: normal; | ||
} | ||
font-family: 'LINESeedKR-Rg'; | ||
font-size: 15px; | ||
color: rgb(59, 56, 56); | ||
`; | ||
|
||
const spanCheckedStyle = css` | ||
@font-face { | ||
font-family: 'LINESeedKR-Rg'; | ||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/LINESeedKR-Rg.woff2') | ||
format('woff2'); | ||
font-weight: 400; | ||
font-style: normal; | ||
} | ||
font-family: 'LINESeedKR-Rg'; | ||
font-size: 15px; | ||
color: gray; | ||
text-decoration: line-through; | ||
`; | ||
|
||
const liStyle = css` | ||
margin: 3px 0px 3px 0px; | ||
`; | ||
|
||
const checkBoxStyle = css` | ||
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. 이렇게 체크박스까지 디테일을 놓치지 않는 것이 디자인적으로 완성도가 매우 높은 것 같습니다.👍🏻 |
||
cursor: pointer; | ||
appearance: none; | ||
width: 12px; | ||
height: 12px; | ||
margin-right: 10px; | ||
border: 1px solid; | ||
border-radius: 10px; | ||
border-color: rgb(59, 56, 56); | ||
|
||
&:checked { | ||
border-color: transparent; | ||
background-size: 100% 100%; | ||
background-color: rgb(247, 196, 218); | ||
} | ||
`; | ||
|
||
const deleteButtonStyle = css` | ||
border: 0; | ||
background-color: white; | ||
font-size: 14px; | ||
color: rgb(59, 56, 56); | ||
margin-left: 3px; | ||
cursor: pointer; | ||
&:hover { | ||
color: rgb(247, 196, 218); | ||
} | ||
`; | ||
|
||
// checkbox를 클릭하면, todoItem의 checked 값이 토글됨 | ||
function onChangeCheckbox() { | ||
const updatedTodoList = todoList.map((item) => ({ | ||
...item, | ||
checked: item.id === todoItem.id ? !item.checked : item.checked, | ||
})); | ||
|
||
setTodoList(updatedTodoList); | ||
} | ||
|
||
// 항목 삭제 | ||
function onDelete(id) { | ||
// 주어진 id와 일치하지 않는 항목들만 남김(일치하면 필터링 -> 해당 항목 삭제) | ||
const updatedTodoList = todoList.filter((todoItem) => todoItem.id !== id); | ||
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. 👍🏻👍🏻 |
||
setTodoList(updatedTodoList); | ||
} | ||
|
||
return ( | ||
<li className="li" css={liStyle}> | ||
<input | ||
type="checkbox" | ||
className="check-box" | ||
checked={todoItem.checked} | ||
onChange={onChangeCheckbox} | ||
css={checkBoxStyle} | ||
/> | ||
<span | ||
className="span" | ||
// 완료 여부에 따라 스타일 변경 | ||
css={todoItem.checked ? spanCheckedStyle : spanStyle} | ||
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. 동적 스타일링까지 아주 좋습니다!! |
||
> | ||
{todoItem.text} | ||
</span> | ||
<button | ||
type="button" | ||
className="delete-button" | ||
onClick={() => onDelete(todoItem.id)} | ||
css={deleteButtonStyle} | ||
> | ||
✖️ | ||
</button> | ||
</li> | ||
); | ||
} | ||
|
||
ToDoItem.propTypes = { | ||
todoItem: PropTypes.shape({ | ||
id: PropTypes.number, | ||
text: PropTypes.string.isRequired, | ||
checked: PropTypes.bool.isRequired, | ||
}), | ||
todoList: PropTypes.arrayOf( | ||
PropTypes.shape({ | ||
id: PropTypes.number.isRequired, | ||
text: PropTypes.string.isRequired, | ||
checked: PropTypes.bool.isRequired, | ||
}) | ||
), | ||
setTodoList: PropTypes.func.isRequired, | ||
}; |
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.
오
normalize
좋습니다!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.
오?
normalize.css
는 처음봐서 찾아보니 정규화를 시켜주고 다른 브라우저로 보더라도 일정한 디자인을 볼 수 있도록 해주기위해서 사용한다고 하는데 좋은거 알아갑니다!!!