Skip to content
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<body>
<div id="root"></div>
<div id="dropdown"></div>
<div id="modal"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
13 changes: 11 additions & 2 deletions src/app.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import { BrowserRouter, Route, Routes } from "react-router";
import ModalProvider from "./components/modal/modal-provider";
import DropdownProvider from "./components/text-field/dropdown-input/dropdown-provider";
import MessagePage from "./pages/message-list";
import TestPage from "./pages/test-page";

function Provider({ children }) {
return (
<ModalProvider>
<DropdownProvider>{children}</DropdownProvider>
</ModalProvider>
);
}

function App() {
return (
<DropdownProvider>
<Provider>
<BrowserRouter>
<Routes>
<Route path="/test-components" element={<TestPage />} />
<Route path="/list" element={<MessagePage />} />
</Routes>
</BrowserRouter>
</DropdownProvider>
</Provider>
);
}

Expand Down
4 changes: 4 additions & 0 deletions src/assets/ic-person.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/components/modal/modal-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createContext } from "react";

const ModalContext = createContext();

export default ModalContext;
10 changes: 10 additions & 0 deletions src/components/modal/modal-provider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useState } from "react";
import ModalContext from "./modal-context";

function ModalProvider({ children }) {
const [showsModal, setShowsModal] = useState(false);
const value = { showsModal, setShowsModal };
return <ModalContext value={value}>{children}</ModalContext>;
}

export default ModalProvider;
208 changes: 208 additions & 0 deletions src/components/modal/modal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { createPortal } from "react-dom";
import styled, { css } from "styled-components";
import defaultProfileImg from "../../assets/ic-person.svg";
import { useModal } from "../../hooks/use-modal";
import { formatDate } from "../../utils/formatter";
import Badge from "../badge/badge";
import BADGE_TYPE from "../badge/badge-type";
import { PrimaryButton } from "../button/button";
import BUTTON_SIZE from "../button/button-size";
import Colors from "../color/colors";

/* ProfileImage */

const profileImageStyle = css`
width: 56px;
height: 56px;
border-radius: 28px;
`;

const UserProfileImage = styled.div`
${profileImageStyle}

img {
width: 100%;
height: 100%;
}
`;

const DefaultProfileImage = styled.div`
${profileImageStyle}
background-color: ${Colors.gray(300)};
display: flex;
justify-content: center;
align-items: center;

img {
width: 32px;
height: 32px;
}
`;

function ProfileImage({ profileImg }) {
const img = <img src={profileImg ?? defaultProfileImg} alt="프로필 사진" />;
return profileImg ? (
<UserProfileImage>{img}</UserProfileImage>
) : (
<DefaultProfileImage>{img}</DefaultProfileImage>
);
}

/* UserInfo */

const Name = styled.span`
font-size: 20px;
font-weight: 400;
line-height: 24px;

span {
font-weight: 700;
}
`;

const StyledUserInfo = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
`;

function UserInfo({ name, type }) {
return (
<StyledUserInfo>
<Name>
From.<span>{` ${name}`}</span>
</Name>
<Badge type={type} />
</StyledUserInfo>
);
}

/* UserProfile */

const StyledUserProfile = styled.div`
display: flex;
align-items: center;
gap: 16px;
`;

function UserProfile({ profileImg, name }) {
return (
<StyledUserProfile>
<ProfileImage profileImg={profileImg} />
<UserInfo name={name} type={BADGE_TYPE.coworker} />
</StyledUserProfile>
);
}

/* Header */

const Date = styled.span`
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: ${Colors.gray(400)};
`;

const StyledHeader = styled.div`
width: 100%;
border-bottom: 1px solid ${Colors.gray(200)};

& > div {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 19px;
}
`;

function Header({ profileImg, name, date }) {
return (
<StyledHeader>
<div>
<UserProfile profileImg={profileImg} name={name} />
<Date>{formatDate(date, ".")}</Date>
</div>
</StyledHeader>
);
}

/* Modal */

const Content = styled.p`
margin: 16px 0 24px;
height: 240px;
overflow: scroll;
font-size: 18px;
font-weight: 400;
line-height: 28px;
color: #5a5a5a;
padding-right: 16px;

&::-webkit-scrollbar {
width: 4px;
height: 0px;
}

&::-webkit-scrollbar-thumb {
background-color: ${Colors.gray(300)};
border-radius: 2px;

&:hover {
background-color: ${Colors.gray(400)};
}
}
`;

const StyledModal = styled.div`
background-color: white;
width: 600px;
border-radius: 16px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
padding: 40px;
display: flex;
flex-direction: column;
align-items: center;
`;

/* Container */

const ModalContainer = styled.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
`;

function Modal({ user, date, content }) {
const { setShowsModal } = useModal();

const ModalPortal = ({ children }) => {
return createPortal(children, document.getElementById("modal"));
};

const handleConfirmClick = () => setShowsModal(false);

return (
<ModalPortal>
<ModalContainer>
<StyledModal>
<Header profileImg={user.profileImg} name={user.name} date={date} />
<Content>{content}</Content>
<PrimaryButton
size={BUTTON_SIZE.medium}
title="확인"
onClick={handleConfirmClick}
/>
</StyledModal>
</ModalContainer>
</ModalPortal>
);
}

export default Modal;
9 changes: 9 additions & 0 deletions src/hooks/use-modal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useContext } from "react";
import ModalContext from "../components/modal/modal-context";

function useModal() {
const { showsModal, setShowsModal } = useContext(ModalContext);
return { showsModal, setShowsModal };
}

export { useModal };
22 changes: 21 additions & 1 deletion src/pages/test-page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import {
} from "../components/button/button";
import BUTTON_SIZE from "../components/button/button-size";
import ToggleButton from "../components/button/toggle-button";
import Modal from "../components/modal/modal";
import TextField from "../components/text-field/text-field";
import TEXT_FIELD_TYPE from "../components/text-field/text-field-type";
import Toast from "../components/toast/toast";
import { useModal } from "../hooks/use-modal";
import { useToast } from "../hooks/use-toast";

function TestPage() {
Expand All @@ -36,7 +38,11 @@ function TestPage() {

const handleToastClick = () => setShowsToast(true);
const handleToastDismiss = () => setShowsToast(false);


/* Modal */
const { showsModal, setShowsModal } = useModal();
const handleModalClick = () => setShowsModal(true);

return (
<div
style={{
Expand Down Expand Up @@ -182,6 +188,20 @@ function TestPage() {
/>
)}
</div>
<div>
<PrimaryButton
size={BUTTON_SIZE.small}
title="Show Modal"
onClick={handleModalClick}
/>
{showsModal && (
<Modal
user={{ name: "김동훈" }}
date={new Date("2023-07-08")}
content="코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요! 코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요! 코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!코로나가 또다시 기승을 부리는 요즘이네요. 건강, 체력 모두 조심 또 하세요!"
/>
)}
</div>
</div>
);
}
Expand Down
8 changes: 8 additions & 0 deletions src/utils/formatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function formatDate(date, token = "-") {
const year = date.getFullYear();
const month = `${date.getMonth() + 1}`.padStart(2, "0");
const day = `${date.getDate()}`.padStart(2, "0");
return `${year}${token}${month}${token}${day}`;
}

export { formatDate };