Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
586ea81
feat [#8] `Toast` component 구현
cskime Aug 11, 2025
7f7d3db
feat [#8] `Toast`를 일정 시간 보여주고 제거하는 `useToast` custom hook 구현
cskime Aug 11, 2025
719e73d
feat [#8] 닫기 버튼으로 `Toast`를 닫을 수 있게 구현
cskime Aug 11, 2025
a59fa05
refactor [] `useDropdown`을 `hooks` 아래로 이동
cskime Aug 12, 2025
f9ae5e5
refactor [] `useToast` 파일 이름을 kebab case로 변경
cskime Aug 12, 2025
0c1df6d
feat [#8] `Toast` component 구현
cskime Aug 11, 2025
6543e98
feat [#8] `Toast`를 일정 시간 보여주고 제거하는 `useToast` custom hook 구현
cskime Aug 11, 2025
3519f0c
feat [#8] 닫기 버튼으로 `Toast`를 닫을 수 있게 구현
cskime Aug 11, 2025
77d369b
refactor [] `useDropdown`을 `hooks` 아래로 이동
cskime Aug 12, 2025
07200f3
refactor [] `useToast` 파일 이름을 kebab case로 변경
cskime Aug 12, 2025
b2dd6b2
feat [#10] `Modal` component UI 구현
cskime Aug 11, 2025
8a82af2
feat [#10] Modal의 전역 상태를 관리하는 context 및 provider 구현
cskime Aug 12, 2025
79888c9
Button 등 trigger를 통해 Modal 띄우기
cskime Aug 12, 2025
47814f5
feat [#15] Header component 구현
cskime Aug 12, 2025
4e7f9d9
feat [#15] `OnboardingLayout` component 구현
cskime Aug 12, 2025
223f7e8
feat [#15] `ContentLayout` component 구현, dummy page 생성 후 연결
cskime Aug 12, 2025
900f8ce
refactor [#15] 이름 변경: `add-post-page` -> `create-post-page`
cskime Aug 12, 2025
32530cd
feat [#15] `OnboardingLayout` 내부에서 '롤링 페이퍼 만들기' 버튼 클릭 시 직접 '/post'로 이동
cskime Aug 12, 2025
8ffcd0a
[#27] list페이지 styled-components로 변경, 카드 arrow버튼 간단하게 구현
nidor022 Aug 13, 2025
915993b
Merge pull request #19 from cskime/feature/#8
cskime Aug 13, 2025
0042163
Merge branch 'develop' into feature/#10
cskime Aug 13, 2025
ee4ad9d
Merge pull request #20 from cskime/feature/#10
cskime Aug 13, 2025
57b1b32
Merge branch 'develop' into feature/#15
cskime Aug 13, 2025
d443219
Merge pull request #21 from cskime/feature/#15
cskime Aug 13, 2025
d663eea
[#27] list페이지 styled-components로 변경, 카드 arrow버튼 간단하게 구현
nidor022 Aug 13, 2025
0961f62
Revert "[#27] list페이지 styled-components로 변경, 카드 arrow버튼 간단하게 구현"
nidor022 Aug 14, 2025
d35aaaf
[#27] pr수정사항 적용, freatures경로 추가
nidor022 Aug 14, 2025
55150b3
Merge remote-tracking branch 'origin/feature/#12' into feature/#27
nidor022 Aug 14, 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
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>
64 changes: 60 additions & 4 deletions src/app.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,74 @@
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 ContentLayout from "./layouts/content-layout";
import OnboardingLayout from "./layouts/onboarding-layout";
import CreatePostPage from "./pages/create-post-page";
import MainPage from "./pages/main-page";
import MessagePage from "./pages/rolling-paper-list-page";
import RecipientPostPage from "./pages/recipient-post-page";
import SendMessagePage from "./pages/send-message-page";
import TestPage from "./pages/test-page";

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

function App() {
return (
<DropdownProvider>
<Provider>
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<OnboardingLayout>
<MainPage />
</OnboardingLayout>
}
/>
<Route
path="/list"
element={
<OnboardingLayout>
<MessagePage />
</OnboardingLayout>
}
/>
<Route path="/post">
<Route
index
element={
<ContentLayout>
<CreatePostPage />
</ContentLayout>
}
/>
<Route
path=":id/message"
element={
<ContentLayout>
<SendMessagePage />
</ContentLayout>
}
/>
<Route
path=":id"
element={
<ContentLayout>
<RecipientPostPage />
</ContentLayout>
}
/>
</Route>
<Route path="/test-components" element={<TestPage />} />
<Route path="/list" element={<MessagePage />} />
</Routes>
</BrowserRouter>
</DropdownProvider>
</Provider>
);
}

Expand Down
3 changes: 3 additions & 0 deletions src/assets/ic-check-circle-green.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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.
3 changes: 3 additions & 0 deletions src/assets/ic-xmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions src/components/header/header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import styled from "styled-components";
import logoImg from "../../assets/logo.svg";
import { media } from "../../utils/media";

const HeaderContent = styled.div`
width: 100%;
max-width: 1200px;
display: flex;
justify-content: space-between;
align-items: center;
`;

const StyledHeader = styled.header`
display: flex;
justify-content: center;
align-items: center;
height: 64px;
border-bottom: 1px solid #ededed;
padding: 0 24px;

${media.mobile} {
padding: 0 16px;
}
`;

function Header({ className, children }) {
return (
<StyledHeader className={className}>
<HeaderContent>
<img src={logoImg} alt="로고" />
<div>{children}</div>
</HeaderContent>
</StyledHeader>
);
}

export default Header;
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;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from "styled-components";
import arrowDownImg from "../../../assets/ic-chevron-down.svg";
import arrowUpImg from "../../../assets/ic-chevron-up.svg";
import { useDropdown } from "../../../hooks/dropdown/use-dropdown";
import { useDropdown } from "../../../hooks/use-dropdown";
import INPUT_STYLES from "../input-styles";
import Dropdown from "./dropdown";
import DropdownOption from "./dropdown-option";
Expand Down
Loading