Skip to content

[2주차] 송아영 미션 제출합니다. #2

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

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
51fa296
init: 프로젝트 생성
gustn99 Mar 18, 2025
b97ff75
chore: 글로벌 스타일 초기화
gustn99 Mar 18, 2025
4be0981
feat: 초기 라우팅 설정
gustn99 Mar 18, 2025
a782662
feat: html을 react로 마이그레이션
gustn99 Mar 18, 2025
26d7533
feat: TodoItem 컴포넌트 분리
gustn99 Mar 18, 2025
743e764
feat: 반응형 추가
gustn99 Mar 18, 2025
1248bc9
feat: 투두 추가 로직 구현
gustn99 Mar 18, 2025
bd11a74
feat: 투두 체크 로직 구현
gustn99 Mar 18, 2025
80d7439
feat: 투두 삭제 로직 구현
gustn99 Mar 18, 2025
53604c5
feat: 잔디 UI 구현
gustn99 Mar 18, 2025
f6d8071
feat: grass 렌더링에 필요한 calendar 구현
gustn99 Mar 18, 2025
25dade1
feat: 로컬스토리지에 투두를 날짜별로 저장하고 불러오는 로직 추가
gustn99 Mar 18, 2025
8f444d4
refactor: twoMonthAgo -> twoMonthsAgo 변수명 수정
gustn99 Mar 18, 2025
93650c4
feat: 지난 날의 투두 완료 여부를 잔디로 표시하는 로직 구현
gustn99 Mar 18, 2025
79fa4a3
refactor: formattedDate 변수 대신 formatDate 함수 사용
gustn99 Mar 18, 2025
bdfbced
fix: 삭제 버튼에 saveTodos 함수 연결
gustn99 Mar 18, 2025
0527380
style: hover 등 전체적인 UI 조정
gustn99 Mar 18, 2025
4c21334
refactor: 사용하지 않는 타입 Omit으로 명시
gustn99 Mar 18, 2025
dac736e
refactor: 절대경로 설정
gustn99 Mar 18, 2025
0844997
fix: 투두 아이템 체크 시 취소선 설정 & html title 설정
gustn99 Mar 18, 2025
21a42e4
refactor: 메모이제이션
gustn99 Mar 18, 2025
ee0ee7f
fix: useCallback 중첩으로 삭제 버튼 클릭 시 allTodos가 제대로 업데이트되지 않는 버그 수정
gustn99 Mar 18, 2025
2b97bba
fix: input에 공백만 입력되는 경우에도 추가 버튼 disabled 처리
gustn99 Mar 18, 2025
c9a296b
feat: 긴 투두 입력이 들어왔을 때 ellipsis 처리
gustn99 Mar 18, 2025
b8633e7
fix: mouseleave 시에도 height 변화에 대해 트랜지션이 적용되도록 수정
gustn99 Mar 19, 2025
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
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 120,
"arrowParens": "always",
"useTabs": true
}
27 changes: 27 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended, 'prettier'],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'@typescript-eslint/no-unused-vars': ['warn'],
'@typescript-eslint/no-explicit-any': 'warn',
},
},
);
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>To Do List</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
40 changes: 40 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "react-todo-21th",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@types/react-router-dom": "^5.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.3.0",
"styled-components": "^6.1.16",
"styled-reset": "^4.5.2"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/node": "^22.13.10",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"prettier": "^3.5.3",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
},
"main": "index.js",
"repository": "https://github.com/gustn99/react-todo-21th.git",
"author": "young <[email protected]>",
"license": "MIT"
}
14 changes: 14 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Home from './pages/Home';

function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
);
}

export default App;
17 changes: 17 additions & 0 deletions src/components/Grass/index.tsx

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image 이거 깃허브처럼 완료하면 해당 날짜에 색깔 채워주는 방식인가요? 제 노트북에서는 왜 grass가 작동 안 할까요ㅠㅠ 이부분 확인해보시면 좋을 것 같습니다!

Copy link
Author

Choose a reason for hiding this comment

The 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,17 @@
import { createGrassCalendar } from '@/utils/createGrassCalendar';
import { GrassItem, GrassLayout, GrassWrapper } from './style';
import { memo, useMemo } from 'react';

export default memo(function Grass() {
const calendar = useMemo(() => createGrassCalendar(), []);

return (
<GrassWrapper>
<GrassLayout>
{calendar.map((date) => (
<GrassItem $hasDoneTodo={date.hasDoneTodo} title={date.date} key={date.date} />
))}
</GrassLayout>
</GrassWrapper>
);
});
31 changes: 31 additions & 0 deletions src/components/Grass/style.tsx
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아영_scroll 처음 들어갔을 때 노트북 뷰에서는 grass의 13번째 줄까지 표시되고 하단이 잘리는 편이라 현재 grass 부분은 보이지 않아서 grass 부분의 용도를 몰랐어요. 하단 부분이 있다는 것을 부모 컴포넌트의 height를 설정하고 overflow로 스크롤을 만들어 알려주어도 좋을 거 같아요.

Copy link
Author

Choose a reason for hiding this comment

The 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,31 @@
import styled from 'styled-components';

export const GrassWrapper = styled.div`
border-left: 2px solid darkslategrey;
padding: 8px;

@media (max-width: 639px) {
display: none;
}
`;

export const GrassLayout = styled.div`
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-auto-rows: minmax(auto);
gap: 4px;
height: fit-content;
`;

export const GrassItem = styled.div<{ $hasDoneTodo: boolean }>`
width: 100%;
aspect-ratio: 1;
margin: auto;
background-color: ${({ $hasDoneTodo }) => ($hasDoneTodo ? 'lightpink' : 'rgb(233, 233, 233)')};
border-radius: 2px;
transition: opacity 0.2s;

&:hover {
opacity: 0.5;
}
`;
7 changes: 7 additions & 0 deletions src/components/TodoItem/dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ChangeEvent } from 'react';
import type { TodoDto } from '@/pages/Home/dto';

export interface TodoItemProps extends Omit<TodoDto, 'date'> {
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
onClickDeleteButton: (id: string) => void;
}
28 changes: 28 additions & 0 deletions src/components/TodoItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useState } from 'react';
import type { TodoItemProps } from './dto';
import { Button, Checkbox, TodoContent, TodoItemLayout } from './style';

export default function TodoItem({ id, isDone, content, onChange, onClickDeleteButton }: TodoItemProps) {
const [isExpended, setIsExpended] = useState(false);
const todoId = String(id);

return (
<TodoItemLayout>
<Checkbox onChange={onChange} checked={isDone} type="checkbox" id={todoId} />
<TodoContent
htmlFor={todoId}
$isDone={isDone}
$isHovered={isExpended}
onMouseEnter={() => {
setIsExpended(true);
}}
onMouseLeave={() => {
setTimeout(() => setIsExpended(false), 700);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기세어 set time out을 사용하신 이유가 무엇인가요?

Copy link
Author

@gustn99 gustn99 Mar 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hover 시 white-space와 max-height를 조절해 transition을 주었는데요, mouseleave 시 white-space가 먼저 변경되면서 max-height에 대한 transition이 적용되지 않는 문제가 있었습니다. mouseleave 시에 white-space보다 max-height가 먼저 줄어들게 함으로써 transition을 적용시키려고 해 봤습니다!

}}
>
{content}
</TodoContent>
<Button onClick={() => onClickDeleteButton(todoId)}>삭제</Button>
</TodoItemLayout>
);
}
57 changes: 57 additions & 0 deletions src/components/TodoItem/style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import styled from 'styled-components';
import { CommonButton } from '@/styles/CommonStyles';
import { memo } from 'react';

export const TodoItemLayout = styled.li`
position: relative;

width: 100%;
padding: 5px 8px;
border-bottom: 1.5px solid darkslategrey;

display: grid;
grid-template-columns: auto 1fr 50px;
gap: 4px;
align-items: center;

transition: background-color 0.2s;

&:hover {
background-color: rgb(245, 245, 245);
}
`;

export const Checkbox = styled.input`
appearance: none;
width: 16px;
height: 16px;
border: 1.5px solid darkslategrey;
border-radius: 2px;
cursor: pointer;

&:checked {
background-color: darkslategrey;
}
`;

export const TodoContent = memo(styled.label<{ $isDone: boolean; $isHovered: boolean }>`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기에 memo 사용하는건 생각도 못했는데 좋은 것 같습니다!

padding: 3px 0;
margin: auto 0;
cursor: pointer;

line-height: 24px;

max-height: 30px;
white-space: ${({ $isHovered }) => ($isHovered ? 'pre-line' : 'nowrap')};
overflow: hidden;
text-overflow: ellipsis;
transition: max-height 1.5s;

${({ $isDone }) => ($isDone ? 'color: rgb(150, 150, 150); text-decoration: line-through;' : '')}

&:hover {
max-height: 300px;
}
`);

export const Button = memo(styled(CommonButton)``);
11 changes: 11 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import GlobalStyles from './styles/GlobalStyles.ts';

createRoot(document.getElementById('root')!).render(
<StrictMode>
<GlobalStyles />
<App />
</StrictMode>,
);
6 changes: 6 additions & 0 deletions src/pages/Home/dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface TodoDto {
id: number;
date: string; // 로컬스토리지 저장을 위해 사용
isDone: boolean;
content: string;
}
Loading