Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
1,020 changes: 1,017 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"typescript-plugin-css-modules": "^5.1.0",
"vite": "^5.2.0"
}
}
52 changes: 52 additions & 0 deletions src/App.css

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이 파일은 module.css로 바꾸지 않은 이유가 있으신가요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

App.css의 내용을 보면 전체에 해당하는 내용을 담고 있습니다.
이는 특정 컴포넌트에서만 적용될 이유가 없기도 하며, 특히 Colors와 같이 어떤 값을 특정 상수로 치환해놓은 정보를 담고 있기에 모듈화 시킬 필요가 없다고 판단했습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이해가 잘 되는 이유네요, App.css에 있는 스타일들은 전역으로 적용되는 의도를 지닌 스타일들이 모여있으므로 .module.css를 붙이지 않는 것이 합리적일 것이라 생각합니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}

ul,
li {
list-style: none;
}

html,
body {
font-family: sans-serif;
font-size: 16px;
}

/* Colors *****************************************/
:root {
--primary-color: #ec4a0a;
--lighten-color: #f6a88a;
--grey-100: #ffffff;
--grey-200: #d0d5dd;
--grey-300: #667085;
--grey-400: #344054;
--grey-500: #000000;
}

/* Typography *************************************/
.text-title {
font-size: 20px;
line-height: 24px;
font-weight: 600;
}

.text-subtitle {
font-size: 18px;
line-height: 28px;
font-weight: 600;
}

.text-body {
font-size: 16px;
line-height: 24px;
font-weight: 400;
}

.text-caption {
font-size: 14px;
line-height: 20px;
font-weight: 400;
}
11 changes: 10 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import "./App.css";
import Aside from "./pages/Aside";
import Body from "./pages/Body";
import Header from "./pages/Header";

function App() {
return <h1>Self-Paced React</h1>;
return(
<>
<Header></Header>
<Body></Body>
<Aside></Aside>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

의견에 따라 갈리는 부분이지만, children이 없는 태그라면 self-closing tag를 사용해 보시는 것에 대해서는 어떻게 생각하시나요!

여기에 더해, <input> 등의 태그의 경우 HTML 태그를 닫지 않아도 문제 없이 사용할 수가 있었잖아요? JSX 문법을 쓰는 현 상황에서는 <Header>와 같이 children이 없는 태그는 닫지 않고 쓸 수는 없는 것인가요?

@ChangwooJ ChangwooJ Mar 30, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

저도 그것이 좋다고 생각합니다!
구체적인 이유가 있는 것은 아니지만 그렇게 하는 편이 가독성에 좋다고 생각해요.

여기에 더해, <input> 등의 태그의 경우 HTML 태그를 닫지 않아도 문제 없이 사용할 수가 있었잖아요? JSX 문법을 쓰는 현 상황에서는 <Header>와 같이 children이 없는 태그는 닫지 않고 쓸 수는 없는 것인가요?

음.. </>태그는 저는 끝을 알려주는 것이라고 알고 있습니다. 그러니까 자바스크립트는 끝이 어디인지 정확히 지정해주어야 하는데.. 솔직히 말씀드리면 input 태그가 닫지 않아도 사용할 수 있는지 몰랐습니다.
일단, JSX문법, 그러니까 곧 JavaScript문법에서는 해당 태그의 끝이 어디인지 알려주어야 하기 때문에 닫지 않고 사용할 수는 없습니다. JSX가 어디서 시작했는지는 알지만 어디서 끝나는지는 모르게 되니 안에 어떤 내용이 들어가는 지도 모르게 되어 오류가 나는 것이죠!
그리고 찾아보니, input이나 img 같은 태그는 Void Elements라고 불립니다.
Void Elements는 내용을 가질 수 없는 태그 입니다. 그렇기 때문에 으로 사용하여야 할 이유도 없으며, 그렇게 해서도 안됩니다.
이 외에 특별한 이유는 찾지 못했지만, 그냥 그렇게 작동되도록 만들어진 것..?으로 보입니다.
찾다보니 알게된 사실인데, 이런 Void Elements들도 JSX에서는 클로징 태그가 필요하다고 합니다. 위에서 말한 이유와 같은 이유로 끝이 어딘지 명시해야하기 때문이라고 합니다.

</>
)
}

export default App;
44 changes: 44 additions & 0 deletions src/component/aside/AddRestaurantModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";
import styles from "../../css/Modal.module.css";

const AddRestaurantModal = () => {
return (
<div className={`${styles["modal"]} ${styles["modal--open"]}`}>
<div className={styles["modal-backdrop"]}></div>
<div className={styles["modal-container"]}>
<h2 className={`${styles["modal-title"]} ${styles["text-title"]}`}>새로운 음식점</h2>
<form>
<div className={`${styles["form-item"]} ${styles["form-item--required"]}`}>
<label htmlFor={`${styles["category"]} ${styles["text-caption"]}`}>카테고리</label>
Comment thread
ChangwooJ marked this conversation as resolved.
Outdated

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  1. htmlFor(React 마이그레이션 전은 for)은 어떨 때 사용할까요? 사용할 경우 어떤 효과를 볼 수 있을까요?
  2. htmlFor의 사용법이 잘못된 것으로 보이는데, htmlFor에는 실제로 어떤 값이 들어가야 할까요? 의도대로 작동하도록 수정해 보실 수 있으신가요?

미션 원본 코드를 그대로 가져오셨다는 것은 저도 알고 있습니다. 그렇지만 짚을만한 좋은 주제라 생각하며 이후 모달의 폼도 관리하실 텐데 그 때쯤이면 다른 분이 리뷰어를 맡고 계시겠죠? 그래서 저는 이번에 이야기해보자 합니다.

@ChangwooJ ChangwooJ Mar 30, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

htmlFor는 label같은 태그가 어떤 요소를 가리키는지를 명시하는 역할을 합니다.
이번 미션을 수행하면서 먼저 컴포넌트를 분리하고 이후에 css작업을 하다보니 문제가 생긴 것 같습니다.
확실히 for를 통해 특정 요소를 가리켜야하는데, 갑자기 css스타일링을 위한 내용이 이 안에 들어가게 되었네요.
이 라벨은 바로 아래에 있는 id="category"를 가리켜야합니다.
그러므로 여기서 의미하는 category는 classname도 아닐뿐더러, category 태그의 스타일이 들어가는 곳도 아닙니다.
그런데 여기서 htmlFor안에 두가지 네임이 들어가있는 것은 이상하게 보입니다.
일단 text-caption은 라벨의 요소 명시를 위한 내용이 아니라 label자체의 내용인 "카테고리"를 스타일링 하기 위한 class로 보입니다.
따라서 제 생각엔 htmlFor="category" className={styles[text-caption]} 으로 수정되어야 할 것 같습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

말씀하신 바가 맞습니다! 잘 조사해주신 것 같아요. 고치신 부분은 확인했지만, 모달에는 총 세 개의 <label>이 있으니 나머지 <label>도 잘못된 htmlFor값 확인 후 수정을 부탁드리며, 수정 이후 <label>을 클릭해 봤을 때 대응되는 요소로 포커싱이 잘 가는지도 확인해 보시기 바랍니다!

<select className={styles["category"]} id="category" required>
<option value="">선택해 주세요</option>
<option value="한식">한식</option>
<option value="중식">중식</option>
<option value="일식">일식</option>
<option value="양식">양식</option>
<option value="아시안">아시안</option>
<option value="기타">기타</option>
</select>
</div>

<div className={`${styles["form-item"]} ${styles["form-item--required"]}`}>
<label htmlFor="name text-caption">이름</label>
Comment thread
ChangwooJ marked this conversation as resolved.
Outdated
<input type="text" name="name" id="name" required />
</div>

<div className={styles["form-item"]}>
<label htmlFor="description text-caption">설명</label>
<textarea name="description" id="description" cols={30} rows={5}></textarea>
<span className={`${styles["help-text"]} text-caption`}>메뉴 등 추가 정보를 입력해 주세요.</span>
</div>

<div className={styles["button-container"]}>
<button className={`${styles["button"]} ${styles["button--primary"]} text-caption`}>추가하기</button>
</div>
</form>
</div>
</div>
)
}

export default AddRestaurantModal;
21 changes: 21 additions & 0 deletions src/component/aside/RestaurantInfoModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";
import styles from "../../css/Modal.module.css";

const RestaurantInfoModal = () => {
return (
<div className={`${styles["modal"]} ${styles["modal--open"]}`}>
<div className={styles["modal-backdrop"]}></div>
<div className={styles["modal-container"]}>
<h2 className={`${styles["modal-title"]} ${styles["text-title"]}`}>음식점 이름</h2>
<div className={styles["restaurant-info"]}>
<p className={`${styles["restaurant-info__description"]} text-body`}>음식점 소개 문구</p>
</div>
<div className={styles["button-container"]}>
<button className={`${styles["button"]} ${styles["button--primary"]} text-caption`}>닫기</button>
</div>
</div>
</div>
)
}

export default RestaurantInfoModal;
20 changes: 20 additions & 0 deletions src/component/body/CategorySortFilter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import styles from "../../css/Body.module.css";

const CategorySortFilter = () => {
return (
<section className={styles["restaurant-filter-container"]}>
<select name="category" id="category-filter" className={`${styles["restaurant-filter"]} ${styles["restaurant-filter-container-select"]}`} aria-label="음식점 카테고리 필터">
<option value="전체">전체</option>
<option value="한식">한식</option>
<option value="중식">중식</option>
<option value="일식">일식</option>
<option value="양식">양식</option>
<option value="아시안">아시안</option>
<option value="기타">기타</option>
</select>
</section>
)
}

export default CategorySortFilter;
77 changes: 77 additions & 0 deletions src/component/body/RestaurantList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from "react";
import styles from "../../css/Body.module.css";

const RestaurantList = () => {
return (
<section className={styles["restaurant-list-container"]}>
<ul className={styles["restaurant-list"]}>
<li className={styles["restaurant"]}>
<div className={styles["restaurant__category"]}>
<img src="../../../templates/category-korean.png" alt="한식" className={styles["category-icon"]} />
Comment thread
ChangwooJ marked this conversation as resolved.
Outdated
</div>
<div className={styles["restaurant__info"]}>
<h3 className={`${styles["restaurant__name"]} text-subtitle`}>피양콩할마니</h3>
<p className={`${styles["restaurant__description"]} text-body`}>평양 출신의 할머니가 수십 년간 운영해온 비지 전문점 피양콩 할마니. 두부를 빼지 않은 되비지를 맛볼 수 있는
곳으로,
‘피양’은 평안도 사투리로 ‘평양’을 의미한다. 딸과 함께 운영하는 이곳에선 맷돌로 직접 간 콩만을 사용하며, 일체의 조미료를 넣지 않은 건강식을 선보인다. 콩비지와 피양 만두가 이곳의 대표
메뉴지만, 할머니가 옛날 방식을 고수하며 만들어내는 비지전골 또한 이 집의 역사를 느낄 수 있는 특별한 메뉴다. 반찬은 손님들이 먹고 싶은 만큼 덜어 먹을 수 있게 준비돼 있다.</p>
</div>
</li>

<li className={styles["restaurant"]}>
<div className={styles["restaurant__category"]}>
<img src="../../../templates/category-chinese.png" alt="중식" className={styles["category-icon"]} />
Comment thread
ChangwooJ marked this conversation as resolved.
Outdated
</div>
<div className={styles["restaurant__info"]}>
<h3 className={`${styles["restaurant__name"]} text-subtitle`}>친친</h3>
<p className={`${styles["restaurant__description"]} text-body`}>Since 2004 편리한 교통과 주차, 그리고 관록만큼 깊은 맛과 정성으로 정통 중식의 세계를 펼쳐갑니다</p>
</div>
</li>

<li className={styles["restaurant"]}>
<div className={styles["restaurant__category"]}>
<img src="../../../templates/category-japanese.png" alt="일식" className={styles["category-icon"]} />
Comment thread
ChangwooJ marked this conversation as resolved.
Outdated
</div>
<div className={styles["restaurant__info"]}>
<h3 className={`${styles["restaurant__name"]} text-subtitle`}>잇쇼우</h3>
<p className={`${styles["restaurant__description"]} text-body`}>잇쇼우는 정통 자가제면 사누끼 우동이 대표메뉴입니다. 기술은 정성을 이길 수 없다는 신념으로 모든 음식에 최선을
다하는 잇쇼우는
고객 한분 한분께 최선을 다하겠습니다</p>
</div>
</li>

<li className={styles["restaurant"]}>
<div className={styles["restaurant__category"]}>
<img src="../../../templates/category-western.png" alt="양식" className={styles["category-icon"]} />
Comment thread
ChangwooJ marked this conversation as resolved.
Outdated
</div>
<div className={styles["restaurant__info"]}>
<h3 className={`${styles["restaurant__name"]} text-subtitle`}>이태리키친</h3>
<p className={`${styles["restaurant__description"]} text-body`}>늘 변화를 추구하는 이태리키친입니다.</p>
</div>
</li>

<li className={styles["restaurant"]}>
<div className={styles["restaurant__category"]}>
<img src="../../../templates/category-asian.png" alt="아시안" className={styles["category-icon"]} />
Comment thread
ChangwooJ marked this conversation as resolved.
Outdated
</div>
<div className={styles["restaurant__info"]}>
<h3 className={`${styles["restaurant__name"]} text-subtitle`}>호아빈 삼성점</h3>
<p className={`${styles["restaurant__description"]} text-body`}>푸짐한 양에 국물이 일품인 쌀국수</p>
</div>
</li>

<li className={styles["restaurant"]}>
<div className={styles["restaurant__category"]}>
<img src="../../../templates/category-etc.png" alt="기타" className={styles["category-icon"]} />
Comment thread
ChangwooJ marked this conversation as resolved.
Outdated
</div>
<div className={styles["restaurant__info"]}>
<h3 className={`${styles["restaurant__name"]} text-subtitle`}>도스타코스 선릉점</h3>
<p className={`${styles["restaurant__description"]} text-body`}>멕시칸 캐주얼 그릴</p>
</div>
</li>
</ul>
</section>
);
};

export default RestaurantList;
86 changes: 86 additions & 0 deletions src/css/Body.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* 카테고리/정렬 필터 */
.restaurant-filter-container {
display: flex;
justify-content: space-between;

padding: 0 16px;
margin-top: 24px;
}

.restaurant-filter-container-select {
height: 44px;
min-width: 125px;

border: 1px solid #d0d5dd;
border-radius: 8px;
background: transparent;

font-size: 16px;
}

.restaurant-filter {
padding: 8px;
}

/* 음식점 목록 */
.restaurant-list-container {
display: flex;
flex-direction: column;

padding: 0 16px;
margin: 16px 0;
}

.restaurant {
display: flex;
align-items: flex-start;

padding: 16px 8px;

border-bottom: 1px solid #e9eaed;
}

.restaurant__category {
display: flex;
justify-content: center;
align-items: center;
width: 64px;
height: 64px;
min-width: 64px;
min-height: 64px;

margin-right: 16px;

border-radius: 50%;
background: var(--lighten-color);
}

.category-icon {
width: 36px;
height: 36px;
}

.restaurant__info {
display: flex;
flex-direction: column;
justify-content: flex-start;
}

.restaurant__name {
margin: 0;
}

.restaurant__distance {
color: var(--primary-color);
}

.restaurant__description {
display: -webkit-box;

padding-top: 8px;

overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
32 changes: 32 additions & 0 deletions src/css/Header.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.gnb {
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;

padding: 0 16px;

background-color: var(--primary-color);
}

.gnb__title {
color: #fcfcfd;
}

.gnb__button {
height: 40px;

border: none;
border-radius: 8px;
background: transparent;

font-size: 24px;
cursor: pointer;
}

.gnb__button__img {
display: block;
width: 40px;
height: 40px;
object-fit: contain;
}
Loading