From c4b60ebc076ffc0af97b4bcd1feb5def14a84c6b Mon Sep 17 00:00:00 2001 From: Nam Yooseong Date: Sun, 7 Jun 2026 17:18:43 +0900 Subject: [PATCH 01/13] =?UTF-8?q?docs:=204=EB=8B=A8=EA=B3=84=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 04-form/README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 04-form/README.md diff --git a/04-form/README.md b/04-form/README.md new file mode 100644 index 0000000..b029ec9 --- /dev/null +++ b/04-form/README.md @@ -0,0 +1,38 @@ +# 04. 폼 UI 구현하기: controlled vs uncontrolled + +## 🎯 요구 사항 +- `Header`의 레스토랑 추가 버튼을 클릭하면 레스토랑 추가 폼이 모달로 뜨도록 구현해 주세요 + - 이전 단계에서 만들어두었던 `AddRestaurantModal`을 그대로 사용합니다. +- 카테고리를 선택하고, ``, ` 메뉴 등 추가 정보를 입력해 주세요. diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 01c061e..512a3c9 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -1,7 +1,7 @@ import styles from "./Header.module.css"; import addButton from "../../assets/add-button.png"; -export default function Header() { +export default function Header({ onAddButtonClick }) { return (

점심 뭐 먹지

@@ -9,6 +9,7 @@ export default function Header() { type="button" className={styles.gnb__button} aria-label="음식점 추가" + onClick={onAddButtonClick} > 음식점 추가 From b218d728ee7899ef81757e776cda6d6d94b3440e Mon Sep 17 00:00:00 2001 From: Nam Yooseong Date: Sun, 7 Jun 2026 21:32:29 +0900 Subject: [PATCH 03/13] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=B0=8F=20props=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 14 +++++++------- .../AddRestaurantModal/AddRestaurantModal.jsx | 12 ++++++------ src/components/CategoryFilter/CategoryFilter.jsx | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index cec5892..8bb6253 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -21,7 +21,7 @@ function App() { const isRestaurantDetailModalOpen = !!clickedRestaurant; const filteredRestaurants = filterRestaurants(restaurants, filterCategory); - function handleChange(e) { + function handleFilterCategoryChange(e) { setFilterCategory(e.target.value); } @@ -29,7 +29,7 @@ function App() { setClickedRestaurant(restaurant); } - function handleModalClose() { + function handleDetailModalClose() { setClickedRestaurant(null); } @@ -78,7 +78,7 @@ function App() {
)} {isAddRestaurantModalOpen && ( @@ -97,9 +97,9 @@ function App() { category={category} name={name} description={description} - handleCategoryChange={handleCategoryChange} - handleNameChange={handleNameChange} - handleDescriptionChange={handleDescriptionChange} + onCategoryChange={handleCategoryChange} + onNameChange={handleNameChange} + onDescriptionChange={handleDescriptionChange} onSubmit={handleSubmit} onClose={handleAddRestaurantModalClose} /> diff --git a/src/components/AddRestaurantModal/AddRestaurantModal.jsx b/src/components/AddRestaurantModal/AddRestaurantModal.jsx index fc8bbb2..1c065fa 100644 --- a/src/components/AddRestaurantModal/AddRestaurantModal.jsx +++ b/src/components/AddRestaurantModal/AddRestaurantModal.jsx @@ -4,9 +4,9 @@ export default function AddRestaurantModal({ category, name, description, - handleCategoryChange, - handleNameChange, - handleDescriptionChange, + onCategoryChange, + onNameChange, + onDescriptionChange, onSubmit, onClose, }) { @@ -24,7 +24,7 @@ export default function AddRestaurantModal({ name="category" id="category" value={category} - onChange={handleCategoryChange} + onChange={onCategoryChange} required > @@ -46,7 +46,7 @@ export default function AddRestaurantModal({ name="name" id="name" value={name} - onChange={handleNameChange} + onChange={onNameChange} required /> @@ -61,7 +61,7 @@ export default function AddRestaurantModal({ cols="30" rows="5" value={description} - onChange={handleDescriptionChange} + onChange={onDescriptionChange} > 메뉴 등 추가 정보를 입력해 주세요. diff --git a/src/components/CategoryFilter/CategoryFilter.jsx b/src/components/CategoryFilter/CategoryFilter.jsx index c2789c5..184edb6 100644 --- a/src/components/CategoryFilter/CategoryFilter.jsx +++ b/src/components/CategoryFilter/CategoryFilter.jsx @@ -1,6 +1,6 @@ import styles from "./CategoryFilter.module.css"; -export default function CategoryFilter({ category, onChangeCategory }) { +export default function CategoryFilter({ category, onCategoryChange }) { return (
setCategory(e.target.value)} - required - > - - - - - - - - - + +
+
+ + +
-
- - setName(e.target.value)} - required - /> -
+
+ + setName(e.target.value)} + required + /> +
-
- - - - 메뉴 등 추가 정보를 입력해 주세요. - -
+
+ + + + 메뉴 등 추가 정보를 입력해 주세요. + +
-
- -
-
- - +
+ +
+ +
); } diff --git a/src/components/AddRestaurantModal/AddRestaurantModal.module.css b/src/components/AddRestaurantModal/AddRestaurantModal.module.css index e9ee9e7..58dd401 100644 --- a/src/components/AddRestaurantModal/AddRestaurantModal.module.css +++ b/src/components/AddRestaurantModal/AddRestaurantModal.module.css @@ -1,36 +1,3 @@ -.modal { - display: none; -} - -.modal--open { - display: block; -} - -.modal__backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - - background: rgba(0, 0, 0, 0.35); -} - -.modal__container { - position: fixed; - bottom: 0; - width: 100%; - - padding: 32px 16px; - - border-radius: 8px 8px 0px 0px; - background: var(--grey-100); -} - -.modal__title { - margin-bottom: 36px; -} - .formItem { display: flex; flex-direction: column; diff --git a/src/components/Modal/Modal.jsx b/src/components/Modal/Modal.jsx new file mode 100644 index 0000000..35507cc --- /dev/null +++ b/src/components/Modal/Modal.jsx @@ -0,0 +1,13 @@ +import styles from "./Modal.module.css"; + +export default function Modal({ children, title, onClose }) { + return ( +
+
+
+

{title}

+ {children} +
+
+ ); +} diff --git a/src/components/Modal/Modal.module.css b/src/components/Modal/Modal.module.css new file mode 100644 index 0000000..8aef078 --- /dev/null +++ b/src/components/Modal/Modal.module.css @@ -0,0 +1,32 @@ +.modal { + display: none; +} + +.modal--open { + display: block; +} + +.modal__backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + + background: rgba(0, 0, 0, 0.35); +} + +.modal__container { + position: fixed; + bottom: 0; + width: 100%; + + padding: 32px 16px; + + border-radius: 8px 8px 0px 0px; + background: var(--grey-100); +} + +.modal__title { + margin-bottom: 36px; +} diff --git a/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx b/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx index 00e7937..93256b1 100644 --- a/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx +++ b/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx @@ -1,25 +1,20 @@ import styles from "./RestaurantDetailModal.module.css"; +import Modal from "../Modal/Modal"; export default function RestaurantDetailModal({ restaurant, onClose }) { return ( -
-
-
-

- {restaurant.name} -

-
-

{restaurant.description}

-
-
- -
+ +
+

{restaurant.description}

-
+
+ +
+ ); } diff --git a/src/components/RestaurantDetailModal/RestaurantDetailModal.module.css b/src/components/RestaurantDetailModal/RestaurantDetailModal.module.css index 0ae7b8d..56deefd 100644 --- a/src/components/RestaurantDetailModal/RestaurantDetailModal.module.css +++ b/src/components/RestaurantDetailModal/RestaurantDetailModal.module.css @@ -1,36 +1,3 @@ -.modal { - display: none; -} - -.modal--open { - display: block; -} - -.modal__backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - - background: rgba(0, 0, 0, 0.35); -} - -.modal__container { - position: fixed; - bottom: 0; - width: 100%; - - padding: 32px 16px; - - border-radius: 8px 8px 0px 0px; - background: var(--grey-100); -} - -.modal__title { - margin-bottom: 36px; -} - .modal__buttonContainer { display: flex; } From e2cc78bdb9b8cb923060b635df35a18da10a9793 Mon Sep 17 00:00:00 2001 From: Nam Yooseong Date: Sun, 7 Jun 2026 23:00:30 +0900 Subject: [PATCH 06/13] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Modal=20=EB=9E=98=ED=8D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/Modal.jsx | 4 ++-- src/components/Modal/Modal.module.css | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/Modal/Modal.jsx b/src/components/Modal/Modal.jsx index 35507cc..1164c19 100644 --- a/src/components/Modal/Modal.jsx +++ b/src/components/Modal/Modal.jsx @@ -2,12 +2,12 @@ import styles from "./Modal.module.css"; export default function Modal({ children, title, onClose }) { return ( -
+ <>

{title}

{children}
-
+ ); } diff --git a/src/components/Modal/Modal.module.css b/src/components/Modal/Modal.module.css index 8aef078..0937846 100644 --- a/src/components/Modal/Modal.module.css +++ b/src/components/Modal/Modal.module.css @@ -1,11 +1,3 @@ -.modal { - display: none; -} - -.modal--open { - display: block; -} - .modal__backdrop { position: fixed; top: 0; From f2d9a318b173c83900fbab97b9a77d452b2a3a42 Mon Sep 17 00:00:00 2001 From: Nam Yooseong Date: Sun, 7 Jun 2026 23:01:05 +0900 Subject: [PATCH 07/13] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 4 ++-- src/components/AddRestaurantModal/AddRestaurantModal.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 4073019..3c4bb06 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -38,7 +38,7 @@ function App() { setIsAddRestaurantModalOpen(false); } - function handleSubmit({ category, name, description }) { + function handleRestaurantSubmit({ category, name, description }) { setRestaurants([ ...restaurants, { @@ -73,7 +73,7 @@ function App() { )} {isAddRestaurantModalOpen && ( )} diff --git a/src/components/AddRestaurantModal/AddRestaurantModal.jsx b/src/components/AddRestaurantModal/AddRestaurantModal.jsx index 081ebe7..8aee4d1 100644 --- a/src/components/AddRestaurantModal/AddRestaurantModal.jsx +++ b/src/components/AddRestaurantModal/AddRestaurantModal.jsx @@ -7,14 +7,14 @@ export default function AddRestaurantModal({ onSubmit, onClose }) { const [name, setName] = useState(""); const [description, setDescription] = useState(""); - function handleSubmit(e) { + function handleFormSubmit(e) { e.preventDefault(); onSubmit({ category, name, description }); } return ( -
+
); diff --git a/src/constants/categories.js b/src/constants/categories.js new file mode 100644 index 0000000..cac9f9e --- /dev/null +++ b/src/constants/categories.js @@ -0,0 +1 @@ +export const CATEGORIES = ["한식", "중식", "일식", "양식", "아시안", "기타"]; From 1693b413e80a405c6b58fa2d75866a430640f121 Mon Sep 17 00:00:00 2001 From: Nam Yooseong Date: Sat, 13 Jun 2026 19:21:51 +0900 Subject: [PATCH 13/13] =?UTF-8?q?refactor:=20RestaurantList=20button=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=ED=98=95=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 4 +- .../RestaurantList/RestaurantList.jsx | 38 +++++++++---------- .../RestaurantList/RestaurantList.module.css | 10 ++++- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 3c4bb06..b07d0c0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -39,8 +39,8 @@ function App() { } function handleRestaurantSubmit({ category, name, description }) { - setRestaurants([ - ...restaurants, + setRestaurants((prev) => [ + ...prev, { id: Date.now(), category, diff --git a/src/components/RestaurantList/RestaurantList.jsx b/src/components/RestaurantList/RestaurantList.jsx index 7fbdb78..0150f7f 100644 --- a/src/components/RestaurantList/RestaurantList.jsx +++ b/src/components/RestaurantList/RestaurantList.jsx @@ -7,26 +7,24 @@ export default function RestaurantList({ restaurants, onRestaurantClick }) {
    {restaurants.map((restaurant) => { return ( -
  • onRestaurantClick(restaurant)} - > -
    - {restaurant.category} -
    -
    -

    - {restaurant.name} -

    -

    - {restaurant.description} -

    -
    +
  • +
  • ); })} diff --git a/src/components/RestaurantList/RestaurantList.module.css b/src/components/RestaurantList/RestaurantList.module.css index a1f4ffe..f7bdbdf 100644 --- a/src/components/RestaurantList/RestaurantList.module.css +++ b/src/components/RestaurantList/RestaurantList.module.css @@ -7,12 +7,20 @@ } .restaurant { + border-bottom: 1px solid #e9eaed; +} + +.restaurant__button { display: flex; align-items: flex-start; + width: 100%; padding: 16px 8px; - border-bottom: 1px solid #e9eaed; + background: none; + border: none; + cursor: pointer; + text-align: left; } .restaurant__category {