ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,63 @@
+package racingcar.view;
+
+import java.util.List;
+import racingcar.domain.car.Car;
+import racingcar.domain.car.Cars;
+
+public class OutputView {
+
+    private OutputView() {
+    }
+
+    public static OutputView getInstance() {
+        return new OutputView();
+    }
+
+    public void printCarNamesInput() {
+        System.out.println(Message.INPUT_CAR_NAMES.getMessage());
+    }
+
+    public void printAttemptsInput() {
+        System.out.println(Message.INPUT_ATTEMPTS_NUMBER.getMessage());
+    }
+
+    public void printExecutionResult() {
+        System.out.printf(Message.EXECUTION_RESULT.getMessage());
+    }
+
+    public void printResult(Cars cars) {
+        for (Car car : cars.getCars()) {
+            System.out.printf(Message.RESULT_FORM.getMessage(), car.getName(),
+                    Message.POSITION_MARK.getMessage().repeat(car.getPosition()));
+        }
+    }
+
+    public void printSpace() {
+        System.out.println();
+    }
+
+    public void printWinners(List<String> winners) {
+        System.out.printf(Message.WINNERS.getMessage(),
+                String.join(Message.WINNERS_DELIMITER.getMessage(), winners));
+    }
+
+    private enum Message {
+        INPUT_CAR_NAMES("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"),
+        INPUT_ATTEMPTS_NUMBER("시도할 회수는 몇회인가요?"),
+        EXECUTION_RESULT("%n실행결과%n"),
+        RESULT_FORM("%s : %s%n"),
+        POSITION_MARK("-"),
+        WINNERS("최종 우승자 : %s%n"),
+        WINNERS_DELIMITER(", ");
+
+        private final String message;
+
+        Message(String message) {
+            this.message = message;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+    }
+}

리뷰: ```suggestion
                    Message.POSITION_MARK
                    .getMessage()
                    .repeat(car.getPosition()));
```
개행을 통해 가독성을 높여주시는게 좋을 것 같습니다!
@@ -0,0 +1,31 @@
+package racingcar.domain.car;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Cars {
+
+    private final List<Car> cars = new ArrayList<>();
+
+    public void addCar(Car car) {
+        cars.add(car);
+    }
+
+    public List<String> getWinners() {
+        return cars.stream()
+                .filter(car -> car.isWinner(getMaxPosition()))
+                .map(Car::getName)
+                .collect(Collectors.toList());
+    }
+
+    private int getMaxPosition() {
+        return Collections.max(cars.stream()
+                .map(Car::getPosition).toList());
+    }
+
+    public List<Car> getCars() {
+        return Collections.unmodifiableList(cars);
+    }
+}

리뷰: 좋은 자료 링크 감사합니다.
@@ -0,0 +1,40 @@
+package racingcar.view;
+
+import camp.nextstep.edu.missionutils.Console;
+import java.util.Arrays;
+import java.util.List;
+import racingcar.util.validator.AttemptsNumberValidator;
+import racingcar.util.validator.CarNamesValidator;
+import racingcar.util.Constant;
+import racingcar.util.validator.Validator;
+
+public class InputView {
+
+    private final Validator namesValidator;
+    private final Validator attempsValidator;
+
+    private InputView() {
+        this.namesValidator = new CarNamesValidator();
+        this.attempsValidator = new AttemptsNumberValidator();
+    }
+
+    public static InputView getInstance() {
+        return new InputView();
+    }
+
+    public List<String> readCarNames() {
+        String input = input();
+        namesValidator.validate(input);
+        return Arrays.asList(input.split(Constant.DELIMITER_COMMA));
+    }
+
+    public int readAttemptsNumber() {
+        String input = input();
+        attempsValidator.validate(input);
+        return Integer.parseInt(input);
+    }
+
+    private String input() {
+        return Console.readLine();
+    }
+}

리뷰: 제가 이름을 오해의 소지가 있도록 작성했네요. 사실 싱글톤도 고민 했었기도 했습니다. 하지만 이번에는 정적 팩토리 메소드 패턴 사용 이유 중 하나인 new를 이용해 객체를 생성하기보다 정적 메소드이름을 통해 생성 목적에 대한 가독성?이라는 측면에서 사용하면 어떤지 의견을 여쭤보기 위해 사용했습니다.
@@ -0,0 +1,35 @@
+package christmas.domain.booking;
+
+import christmas.domain.booking.dto.MenuItem;
+import christmas.domain.booking.dto.MenuType;
+import java.util.Arrays;
+import java.util.List;
+
+public class Menu {
+    // APPETIZER
+    public static final MenuItem APPETIZER_1 = new MenuItem(MenuType.APPETIZER, "양송이수프", 6_000);
+    public static final MenuItem APPETIZER_2 = new MenuItem(MenuType.APPETIZER, "타파스", 5_500);
+    public static final MenuItem APPETIZER_3 = new MenuItem(MenuType.APPETIZER, "시저샐러드", 8_000);
+
+    // MAIN
+    public static final MenuItem MAIN_1 = new MenuItem(MenuType.MAIN, "티본스테이크", 55_000);
+    public static final MenuItem MAIN_2 = new MenuItem(MenuType.MAIN, "바비큐립", 54_000);
+    public static final MenuItem MAIN_3 = new MenuItem(MenuType.MAIN, "해산물파스타", 35_000);
+    public static final MenuItem MAIN_4 = new MenuItem(MenuType.MAIN, "크리스마스파스타", 25_000);
+
+    // DESSERT
+    public static final MenuItem DESSERT_1 = new MenuItem(MenuType.DESSERT, "초코케이크", 15_000);
+    public static final MenuItem DESSERT_2 = new MenuItem(MenuType.DESSERT, "아이스크림", 5_000);
+
+    // BEVERAGE
+    public static final MenuItem BEVERAGE_1 = new MenuItem(MenuType.BEVERAGE, "제로콜라", 3_000);
+    public static final MenuItem BEVERAGE_2 = new MenuItem(MenuType.BEVERAGE, "레드와인", 60_000);
+    public static final MenuItem BEVERAGE_3 = new MenuItem(MenuType.BEVERAGE, "샴페인", 25_000);
+
+    public static final List<MenuItem> MENU_ITEMS = Arrays.asList(
+            APPETIZER_1, APPETIZER_2, APPETIZER_3,
+            MAIN_1, MAIN_2, MAIN_3, MAIN_4,
+            DESSERT_1, DESSERT_2,
+            BEVERAGE_1, BEVERAGE_2, BEVERAGE_3
+    );
+}

리뷰: Enum을 활용해서 메뉴를 구현해보시는 것도 추천드려요!
@@ -0,0 +1,33 @@
+package christmas.domain.payment.discount;
+
+import static christmas.domain.payment.constants.Constant.PRESENT_YEAR;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.Month;
+
+public enum DayType {
+    WEEKDAY,
+    WEEKEND;
+
+    public static boolean isWeekday(int day) {
+        LocalDate orderDate = LocalDate.of(PRESENT_YEAR, Month.DECEMBER, day);
+        DayOfWeek dayOfWeek = orderDate.getDayOfWeek();
+        if (isSunday(day)) {
+            return true;
+        }
+        return dayOfWeek.getValue() <= DayOfWeek.THURSDAY.getValue();
+    }
+
+    public static boolean isSunday(int day) {
+        LocalDate orderDate = LocalDate.of(PRESENT_YEAR, Month.DECEMBER, day);
+        DayOfWeek dayOfWeek = orderDate.getDayOfWeek();
+        return dayOfWeek.getValue() == DayOfWeek.SUNDAY.getValue();
+    }
+
+    public static boolean isWeekend(int day) {
+        LocalDate orderDate = LocalDate.of(PRESENT_YEAR, Month.DECEMBER, day);
+        DayOfWeek dayOfWeek = orderDate.getDayOfWeek();
+        return dayOfWeek.getValue() >= DayOfWeek.FRIDAY.getValue();
+    }
+}

리뷰: 나머지 연산을 활용한다면 할인 조건에 필요한 요일을 구하는 연산을 더 간결하게 작성할 수 있을 것 같습니다.
@@ -0,0 +1,35 @@
+package christmas.view.output;
+
+import static christmas.domain.booking.constants.Message.AMOUNT_BEFORE_DISCOUNT;
+import static christmas.domain.booking.constants.Message.ORDER;
+import static christmas.domain.booking.constants.Message.ORDER_DETAIL;
+import static christmas.domain.booking.constants.Message.RECEPTION;
+
+import christmas.domain.booking.dto.BookMessage;
+import java.text.DecimalFormat;
+
+public class BookingWriterView extends OutputView {
+    private BookingWriterView() {
+    }
+
+    public static void MenuOrder(BookMessage bookMessage) {
+        String message = String.format(RECEPTION.getMessage(), bookMessage.reservationDay());
+        long[] amount = {0};
+        bookMessage.menuAndAmountMap().forEach((key, value) -> {
+            amount[0] += (long) key.price() * value;
+        });
+        println(message);
+        println(ORDER.getMessage());
+        bookMessage.menuAndAmountMap().forEach((key, value) -> {
+            println(String.format(ORDER_DETAIL.getMessage(), key.name(), value));
+
+        });
+        totalRawPrice(amount);
+    }
+
+    private static void totalRawPrice(long[] amount) {
+        DecimalFormat df = new DecimalFormat("#,###");
+        String formatted = df.format(amount[0]);
+        println(String.format(AMOUNT_BEFORE_DISCOUNT.getMessage(), formatted));
+    }
+}

리뷰: 오 static method 밖에 없어 외부에서 객체를 생성하는 것을 막으셨군요!
@@ -0,0 +1,35 @@
+package christmas.view.output;
+
+import static christmas.domain.booking.constants.Message.AMOUNT_BEFORE_DISCOUNT;
+import static christmas.domain.booking.constants.Message.ORDER;
+import static christmas.domain.booking.constants.Message.ORDER_DETAIL;
+import static christmas.domain.booking.constants.Message.RECEPTION;
+
+import christmas.domain.booking.dto.BookMessage;
+import java.text.DecimalFormat;
+
+public class BookingWriterView extends OutputView {
+    private BookingWriterView() {
+    }
+
+    public static void MenuOrder(BookMessage bookMessage) {
+        String message = String.format(RECEPTION.getMessage(), bookMessage.reservationDay());
+        long[] amount = {0};
+        bookMessage.menuAndAmountMap().forEach((key, value) -> {
+            amount[0] += (long) key.price() * value;
+        });
+        println(message);
+        println(ORDER.getMessage());
+        bookMessage.menuAndAmountMap().forEach((key, value) -> {
+            println(String.format(ORDER_DETAIL.getMessage(), key.name(), value));
+
+        });
+        totalRawPrice(amount);
+    }
+
+    private static void totalRawPrice(long[] amount) {
+        DecimalFormat df = new DecimalFormat("#,###");
+        String formatted = df.format(amount[0]);
+        println(String.format(AMOUNT_BEFORE_DISCOUNT.getMessage(), formatted));
+    }
+}

리뷰: - 프리코스 1주차 공통 피드백 중
> 축약하지 않는다
의도를 드러낼 수 있다면 이름이 길어져도 괜찮다.
누구나 실은 클래스, 메서드, 또는 변수의 이름을 줄이려는 유혹에 곧잘 빠지곤 한다. 그런 유혹을 뿌리쳐라. 축약은 혼란을 야기하며, 더 큰 문제를 숨기는 경향이 있다. 클래스와 메서드 이름을 한 두 단어로 유지하려고 노력하고 문맥을 중복하는 이름을 자제하자. 클래스 이름이 Order라면 shipOrder라고 메서드 이름을 지을 필요가 없다. 짧게 ship()이라고 하면 클라이언트에서는 order.ship()라고 호출하며, 간결한 호출의 표현이 된다. 
객체 지향 생활 체조 원칙 5: 줄여쓰지 않는다 (축약 금지)

- `new DecimalFormat("#,###")`은 매번 생성하지 않아도 되므로 `private static final`로 클래스 변수로 선언해서 쓰시는 것을 추천드립니다!
@@ -0,0 +1,33 @@
+package christmas.controller;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import christmas.domain.booking.dto.MenuItem;
+import christmas.domain.booking.dto.MenuType;
+import christmas.service.Parser;
+import christmas.service.Payment;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+class PaymentControllerTest {
+
+    @Test
+    void getRawTotal() {
+        // given
+        int reservationDay = 26;
+        Map<MenuItem, Integer> expected = new HashMap<>();
+        Map<MenuItem, Integer> menuAndAmountMap = new HashMap<>();
+        MenuItem MenuItem1 = new MenuItem(MenuType.MAIN, "티본스테이크", 55_000);
+        MenuItem MenuItem2 = new MenuItem(MenuType.MAIN, "바비큐립", 54_000);
+        expected.put(MenuItem1, 1);
+        expected.put(MenuItem2, 1);
+        menuAndAmountMap = Parser.splitMenuAndAmount("티본스테이크-1,바비큐립-1");
+        Payment payment = new Payment(reservationDay, menuAndAmountMap);
+        // when
+        int expectedTotal = 109_000;
+        int actualTotal = payment.getRawTotal(menuAndAmountMap);
+        // then
+        assertEquals(expectedTotal, actualTotal);
+    }
+}
\ No newline at end of file

리뷰: - 메서드의 이름을 통해 해당 코드가 어떤 상황에서 무엇을 테스트 하시는지 나타내시는 것을 추천드립니다. 나중에 다른사람이 와서 보더라도 뭘 테스트하는지 알아보기 쉬워야 하니까요
- `assertEquals()`보다는 `assertThat().isEqualTo()`를 사용하시는 것을 추천드립니다. 영어 문장 순서와 동일하게 되어있어 훨씬 읽기 쉽습니다.
- 아래 코드는 제가 작성한 테스트 예시입니다.
```java
    @ParameterizedTest(name = "{0}; 디저트 메뉴 개수 : {1}")
    @MethodSource
    @DisplayName("주문한 총 디저트 메뉴 개수를 셀 수 있다")
    void countDessertMenuTest(Map<Menu, Integer> menuToCount, int expected) {
        Order order = Order.from(menuToCount);

        int actual = order.countDessertMenu();

        assertThat(actual).isEqualTo(expected);
    }

    private static Stream<Arguments> countDessertMenuTest() {
        return Stream.of(
                Arguments.of(Map.of(DRINK_EXAMPLE, 3, DESSERT_EXAMPLE, 4), 4),
                Arguments.of(Map.of(MAIN_EXAMPLE, 5, APPETIZER_EXAMPLE, 3), 0),
                Arguments.of(Map.of(Menu.CHOCOLATE_CAKE, 6, Menu.ICE_CREAM, 4), 10)
        );
    }
```
@@ -0,0 +1,101 @@
+import React, { useEffect } from 'react';
+import styled from 'styled-components';
+import { BiMessageAlt } from 'react-icons/bi';
+import { useIssues } from '../hooks/useIssues';
+import { IssueSchema } from '../types/issuesApi';
+import { Link } from 'react-router-dom';
+import AD from '../components/AD';
+import InfiniteScroll from '../components/InfiniteScroll';
+
+function Times(date: number) {
+  let times;
+  const time = Math.floor(date / (1000 * 60 * 60)); // 시간
+  const day = Math.floor(date / (1000 * 60 * 60 * 24)); // 일
+  const year = Math.floor(date / (1000 * 60 * 60 * 24 * 365)); // 년
+
+  if (year > 0) {
+    times = `${year}년 전`;
+  } else if (day > 0) {
+    times = `${day}일 전`;
+  } else {
+    times = `${time}시간 전`;
+  }
+  return times;
+}
+
+function Home() {
+  const { issueList, fetchIssues, isLoading } = useIssues();
+
+  useEffect(() => {
+    fetchIssues();
+  }, []);
+
+  return (
+    <>
+      {issueList?.map((issue: IssueSchema, index: number) => {
+        const currentTime = new Date();
+        const createdAt = new Date(issue.created_at);
+        const timeDifference = currentTime.getTime() - createdAt.getTime();
+
+        return (
+          <React.Fragment key={issue.id}>
+            <InfiniteScroll>
+              <Container>
+                <Issues>
+                  <Item>
+                    <IssueLink to={`/issues/${issue.number}`}>{issue.title}</IssueLink>
+                    <OpenedBy>
+                      #{issue.number} opened {Times(timeDifference)} by {issue.user.login}
+                    </OpenedBy>
+                  </Item>
+                  <Comment>
+                    <BiMessageAlt /> {issue.comments}
+                  </Comment>
+                </Issues>
+              </Container>
+              {<AD key={index} index={index} />}
+            </InfiniteScroll>
+          </React.Fragment>
+        );
+      })}
+      {isLoading && <span>Loading...</span>}
+    </>
+  );
+}
+
+export default Home;
+
+const Container = styled.div`
+  border: 1px solid black;
+  height: auto;
+  padding: 8px;
+  &:hover {
+    background-color: lightgray;
+  }
+`;
+
+const Issues = styled.li`
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+`;
+
+const Item = styled.div`
+  display: block;
+  flex: auto;
+`;
+
+const IssueLink = styled(Link)`
+  font-weight: bold;
+`;
+
+const OpenedBy = styled.span`
+  font-size: small;
+  margin-top: 1px;
+  display: flex;
+  color: whitegray;
+`;
+
+const Comment = styled.div`
+  white-space: nowrap;
+`;

리뷰: 최초 로딩은 잘 보이는데, 무한 스크롤시 로딩이 잘려서 그런건지 잘 안보이는 것 같은데 맞나요? 인터넷 느리게 변경해도 안보이네요 ㅠㅠ
@@ -0,0 +1,15 @@
+import { Outlet } from 'react-router-dom';
+import { styled } from 'styled-components';
+
+const Title = styled.h1`
+  text-align: center;
+`;
+
+export function Header() {
+  return (
+    <>
+      <Title>facebook/react</Title>
+      <Outlet />
+    </>
+  );
+}

리뷰: 컴포넌트명과 파일명이 일치하는게 좋을 것 같습니다
또 강사님이 facebook/react를 하드코딩 안하는 방식이 좋을 것 같다고 하셔서 
다른 방법도 생각해보시면 좋을것 같네요 :)
@@ -0,0 +1,41 @@
+import React from 'react';
+import { useIssue } from '../hooks/useIssue';
+import { useParams } from 'react-router-dom';
+import { styled } from 'styled-components';
+import MarkdownViewer from '../components/MarkDownViewer';
+
+function Detail() {
+  const { issue, fetchIssue, isLoading } = useIssue();
+  const { id } = useParams();
+
+  React.useEffect(() => {
+    if (id) {
+      const parsedId = parseInt(id);
+      fetchIssue(parsedId);
+    }
+  }, []);
+
+  if (isLoading) return <div>Loading...</div>;
+  return (
+    <>
+      <Title>
+        <bdi>{issue?.title}</bdi>
+        <span>#{issue?.number}</span>
+      </Title>
+      <br />
+      <>
+        <MarkdownViewer markdown={issue?.body} />
+      </>
+    </>
+  );
+}
+
+export default Detail;
+
+const Title = styled.h1`
+  span {
+    color: gray;
+    font-size: 0.7em;
+    margin-left: 5px;
+  }
+`;

리뷰: 과제 페이지에서 이슈 상세 화면쪽 구현해야 하는 내용을 보면

`이슈번호, 이슈제목, 작성자, 작성일, 코멘트 수, 작성자 프로필 이미지, 본문 표시` 라고 되어있습니다.  issue정보들을 더 추가해야 하지 않을까 싶어요
@@ -0,0 +1,41 @@
+import React from 'react';
+import { useIssue } from '../hooks/useIssue';
+import { useParams } from 'react-router-dom';
+import { styled } from 'styled-components';
+import MarkdownViewer from '../components/MarkDownViewer';
+
+function Detail() {
+  const { issue, fetchIssue, isLoading } = useIssue();
+  const { id } = useParams();
+
+  React.useEffect(() => {
+    if (id) {
+      const parsedId = parseInt(id);
+      fetchIssue(parsedId);
+    }
+  }, []);
+
+  if (isLoading) return <div>Loading...</div>;
+  return (
+    <>
+      <Title>
+        <bdi>{issue?.title}</bdi>
+        <span>#{issue?.number}</span>
+      </Title>
+      <br />
+      <>
+        <MarkdownViewer markdown={issue?.body} />
+      </>
+    </>
+  );
+}
+
+export default Detail;
+
+const Title = styled.h1`
+  span {
+    color: gray;
+    font-size: 0.7em;
+    margin-left: 5px;
+  }
+`;

리뷰: 아.. 그러네요 제가 저 부분을 못봤던거 같습니다
@@ -0,0 +1,21 @@
+import styled from "styled-components";
+import { flexCenter } from "@/styles/common";
+import { Product } from "@/types/products";
+import ItemCartMemo from "@/components/ItemCard";
+
+const ItemCardList = ({ products }: { products: Product[] }) => {
+  return (
+    <ItemCardWrapper>
+      {products && products.map((product) => <ItemCartMemo key={`${product.id}`} product={product} />)}
+    </ItemCardWrapper>
+  );
+};
+
+export default ItemCardList;
+
+const ItemCardWrapper = styled.div`
+  display: flex;
+  gap: 14px;
+  flex-wrap: wrap;
+  ${flexCenter}
+`;

리뷰: key 값을 이렇게 랜덤으로 관리를 하셨군요 ! 저도 key 값에 대한 고민이 있었는데 보통 이 방법이 보편적으로 사용되는 방법인지 궁금합니다 😉
@@ -0,0 +1,21 @@
+import styled from "styled-components";
+import { flexCenter } from "@/styles/common";
+import { Product } from "@/types/products";
+import ItemCartMemo from "@/components/ItemCard";
+
+const ItemCardList = ({ products }: { products: Product[] }) => {
+  return (
+    <ItemCardWrapper>
+      {products && products.map((product) => <ItemCartMemo key={`${product.id}`} product={product} />)}
+    </ItemCardWrapper>
+  );
+};
+
+export default ItemCardList;
+
+const ItemCardWrapper = styled.div`
+  display: flex;
+  gap: 14px;
+  flex-wrap: wrap;
+  ${flexCenter}
+`;

리뷰: 리뷰어에게 물어봤더니 UUID를 추천하시더라구요! 썬데이처럼 'product-id' 를 사용해서 동적인 id를 생성해주는 것도 정말 좋은 것 같아서 그렇게 바꿔보려구요! 감사합니다
@@ -0,0 +1,31 @@
+import {
+  ModalBody,
+  ModalButtonContainer,
+  ModalCloseButton,
+  ModalContainer,
+  ModalDescription,
+  ModalDimmedLayer,
+  ModalHeader,
+  ModalInputField,
+  ModalTitle,
+} from './index';
+
+interface ModalProp {
+  children: JSX.Element;
+}
+
+const Modal = ({ children }: ModalProp) => {
+  return <>{children}</>;
+};
+
+Modal.Body = ModalBody;
+Modal.ButtonContainer = ModalButtonContainer;
+Modal.CloseButton = ModalCloseButton;
+Modal.Container = ModalContainer;
+Modal.Description = ModalDescription;
+Modal.DimmedLayer = ModalDimmedLayer;
+Modal.Header = ModalHeader;
+Modal.InputField = ModalInputField;
+Modal.Title = ModalTitle;
+
+export default Modal;

리뷰: 혹시 이번 미션에서 Compound Component 사용에 대한 고민이 있으셨을까요?

지금 Modal의 인터페이스를 보면, 모든 옵션들이 prop으로 들어오고 있어서 관심사 별로 나누어 확인하는 데 오래걸리는 것 같아요..! 예를 들면, 모달의 외적인 요소인 size, position을 묶어서 생각해야 될 때 그 둘을 한 눈에 파악하기 어려운 것 같습니다!

만약 props으로 관리한다면, 관심사 별로 조금 뭉쳐보면 어떨까요? 아래 예시 코드 처럼요!
```typescript
interface ModalProps {
    isOpened: boolean;
    size?: ModalSize;
    modalPosition?: ModalPosition;

    title?: string;
    showCloseButton?: boolean;

    description?: string;

    buttonPosition?: ButtonPosition;
    primaryButton?: ButtonProps;
    secondaryButton?: ButtonProps;
    primaryColor?: string;

    children?: JSX.Element;
    
    onClose: () => void;
}
```
@@ -0,0 +1,31 @@
+import {
+  ModalBody,
+  ModalButtonContainer,
+  ModalCloseButton,
+  ModalContainer,
+  ModalDescription,
+  ModalDimmedLayer,
+  ModalHeader,
+  ModalInputField,
+  ModalTitle,
+} from './index';
+
+interface ModalProp {
+  children: JSX.Element;
+}
+
+const Modal = ({ children }: ModalProp) => {
+  return <>{children}</>;
+};
+
+Modal.Body = ModalBody;
+Modal.ButtonContainer = ModalButtonContainer;
+Modal.CloseButton = ModalCloseButton;
+Modal.Container = ModalContainer;
+Modal.Description = ModalDescription;
+Modal.DimmedLayer = ModalDimmedLayer;
+Modal.Header = ModalHeader;
+Modal.InputField = ModalInputField;
+Modal.Title = ModalTitle;
+
+export default Modal;

리뷰: 오.. 바다가 공유해 준 createPortal을 사용하셨군요! createPortal을 사용하면서 얻은 인사이트나 느낀 바가 있다면 공유해주시면 정말 좋을 것 같아요~~~

또, Modal 컴포넌트의 UI 로직을 정말 잘 나눠서 한 눈에 확 보여서 좋습니다!
@@ -0,0 +1,31 @@
+import {
+  ModalBody,
+  ModalButtonContainer,
+  ModalCloseButton,
+  ModalContainer,
+  ModalDescription,
+  ModalDimmedLayer,
+  ModalHeader,
+  ModalInputField,
+  ModalTitle,
+} from './index';
+
+interface ModalProp {
+  children: JSX.Element;
+}
+
+const Modal = ({ children }: ModalProp) => {
+  return <>{children}</>;
+};
+
+Modal.Body = ModalBody;
+Modal.ButtonContainer = ModalButtonContainer;
+Modal.CloseButton = ModalCloseButton;
+Modal.Container = ModalContainer;
+Modal.Description = ModalDescription;
+Modal.DimmedLayer = ModalDimmedLayer;
+Modal.Header = ModalHeader;
+Modal.InputField = ModalInputField;
+Modal.Title = ModalTitle;
+
+export default Modal;

리뷰: 모달 내부의 각 섹션을 컴포넌트로 분리하여 재사용 한 점이 인상적입니다!

컴포넌트를 잘 나누어서 Alert, Confirm, Prompt와 같은 추가적인 요구사항이 와도 큰 문제없이 대응할 수 있겠네요!!
@@ -0,0 +1,31 @@
+import {
+  ModalBody,
+  ModalButtonContainer,
+  ModalCloseButton,
+  ModalContainer,
+  ModalDescription,
+  ModalDimmedLayer,
+  ModalHeader,
+  ModalInputField,
+  ModalTitle,
+} from './index';
+
+interface ModalProp {
+  children: JSX.Element;
+}
+
+const Modal = ({ children }: ModalProp) => {
+  return <>{children}</>;
+};
+
+Modal.Body = ModalBody;
+Modal.ButtonContainer = ModalButtonContainer;
+Modal.CloseButton = ModalCloseButton;
+Modal.Container = ModalContainer;
+Modal.Description = ModalDescription;
+Modal.DimmedLayer = ModalDimmedLayer;
+Modal.Header = ModalHeader;
+Modal.InputField = ModalInputField;
+Modal.Title = ModalTitle;
+
+export default Modal;

리뷰: 네 맞아요! Compound Component를 사용하면, 주어진 alert, comfirm, prompt, 그리고 추후 추가적으로 발생할 수 있는 요구사항 modal type에 맞춰서 손쉽게 대응이 가능하지 않을까 생각했습니다!

다만, compound component에 지식이 부족했고 여기에 쏟을 시간이 부족해서 compound component의 장점을 제대로 살리지 못했던 것 같네요!

interface를 작성하고 docs를 작성할 때 저 스스로도 순서가 헷갈리고 어떻게 정렬해야 좋을지 고민이었는데 말씀해 주신 대로 관심사 별로 뭉쳐서 작성하는게 좋은 방법이 될 것 같아요!
@@ -0,0 +1,31 @@
+import {
+  ModalBody,
+  ModalButtonContainer,
+  ModalCloseButton,
+  ModalContainer,
+  ModalDescription,
+  ModalDimmedLayer,
+  ModalHeader,
+  ModalInputField,
+  ModalTitle,
+} from './index';
+
+interface ModalProp {
+  children: JSX.Element;
+}
+
+const Modal = ({ children }: ModalProp) => {
+  return <>{children}</>;
+};
+
+Modal.Body = ModalBody;
+Modal.ButtonContainer = ModalButtonContainer;
+Modal.CloseButton = ModalCloseButton;
+Modal.Container = ModalContainer;
+Modal.Description = ModalDescription;
+Modal.DimmedLayer = ModalDimmedLayer;
+Modal.Header = ModalHeader;
+Modal.InputField = ModalInputField;
+Modal.Title = ModalTitle;
+
+export default Modal;

리뷰: 우선 createPortal을 이용해서 document.body로 위치를 옮겨주다 보니 다른 stack context가 되어서 z-Index 관리에 유용하다는 점이었어요!

만약 사용하는 유저가
```xml
<body>
    <app>
        <Modal />
        <OtherComponent />
    </app>
</body>
```
와 같이 사용한다고 했을 때, 저희가 제공하는 modal의 zindex가 300 이고, use의 OtherComponent의 zindex가 500이라면 이 둘은 같은 stack context에 있기 때문에, modal 위로 OtherComponent가 올라올 수 있는 문제점이 있겠죠!

실제로 step1에서 이런 피드백을 받았고, step1의 리뷰 반영에서는 Modal에 zindex를 prop으로 받아서 사용할 수 있게 했었어요.
하지만 결국 사용자는 component들과 modal의 zindex들에 대해서 많은 이해가 필요하게 되기 때문에 새로운 component를 추가하려고 할 때도 이를 고민해야 할 것 같았어요.

```xml
<body>
    <app>
        <OtherComponent />
    </app>
    <Modal />
</body>
```

하지만 createPortal을 이용해 document.body로 물리적인 위치를 변경해주면, Modal은 app과 같은 stack context 에 있기 때문에, app의 zindex보다 높기만 하면 app 내부의 어떤 component보다도 위에 위치할 수 있게 되어서 이부분이 좋았던 것 같습니다!